]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
8845451183f1151d6ab234d3e4669c8b9fcedd85
[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 "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"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
42
43 #include <fcntl.h>
44
45 #include <cerrno>
46 #include <cstdlib>
47 #include <cstdio>
48
49 #include <utility>
50 #include <fstream>
51 #include <sstream>
52
53 // FIXME Availability?
54 #include <utime.h>  // for utimbuf
55
56 #ifdef HAVE_UNISTD_H
57 # include <unistd.h>
58 #endif
59 #ifdef HAVE_DIRECT_H
60 # include <direct.h>
61 #endif
62 #ifdef HAVE_SYS_TYPES_H
63 # include <sys/types.h>
64 #endif
65 #ifdef HAVE_SYS_STAT_H
66 # include <sys/stat.h>
67 #endif
68 #ifdef HAVE_IO_H
69 # include <io.h>
70 #endif
71 #ifdef HAVE_SYS_UTIME_H
72 # include <sys/utime.h>
73 #endif
74 #ifdef HAVE_UTIME_H
75 # include <utime.h>
76 #endif
77
78 #include "zip.h"
79 #include "unzip.h"
80
81 #ifdef WIN32
82 #define USEWIN32IOAPI
83 #include "iowin32.h"
84 #endif
85
86 #define WRITEBUFFERSIZE (16384)
87 #define MAXFILENAME (256)
88
89
90 #ifndef CXX_GLOBAL_CSTD
91 using std::fgetc;
92 #endif
93
94 using std::endl;
95 using std::getline;
96 using std::make_pair;
97 using std::string;
98 using std::ifstream;
99 using std::ostringstream;
100 using std::vector;
101 using std::pair;
102
103 namespace fs = boost::filesystem;
104
105 namespace lyx {
106 namespace support {
107
108 bool isLyXFilename(string const & filename)
109 {
110         return suffixIs(ascii_lowercase(filename), ".lyx");
111 }
112
113
114 bool isSGMLFilename(string const & filename)
115 {
116         return suffixIs(ascii_lowercase(filename), ".sgml");
117 }
118
119
120 bool isValidLaTeXFilename(string const & filename)
121 {
122         string const invalid_chars("#$%{}()[]\"^");
123         if (filename.find_first_of(invalid_chars) != string::npos)
124                 return false;
125         else
126                 return true;
127 }
128
129
130 string const latex_path(string const & original_path,
131                 latex_path_extension extension,
132                 latex_path_dots dots)
133 {
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() ?
145                                 path :
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;
150                 } else {
151                         path = "\\string\"" + path + "\\string\"";
152                 }
153         }
154
155         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
156 }
157
158
159 // Substitutes spaces with underscores in filename (and path)
160 string const makeLatexName(string const & file)
161 {
162         string name = onlyFilename(file);
163         string const path = onlyPath(file);
164
165         // ok so we scan through the string twice, but who cares.
166         string const keep = "abcdefghijklmnopqrstuvwxyz"
167                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
168                 "@!'()*+,-./0123456789:;<=>?[]`|";
169
170         string::size_type pos = 0;
171         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
172                 name[pos++] = '_';
173
174         return addName(path, name);
175 }
176
177
178 string const quoteName(string const & name, quote_style style)
179 {
180         switch(style) {
181         case quote_shell:
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
188                 // limitations.
189                 return (os::shell() == os::UNIX) ?
190                         '\'' + name + '\'':
191                         '"' + name + '"';
192         case quote_python:
193                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
194                      + "\"";
195         }
196         // shut up stupid compiler
197         return string();
198 }
199
200
201 bool isFileReadable(FileName const & filename)
202 {
203         std::string const path = filename.toFilesystemEncoding();
204         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
205 }
206
207
208 //returns true: dir writeable
209 //        false: not writeable
210 bool isDirWriteable(FileName const & path)
211 {
212         LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
213
214         FileName const tmpfl(tempName(path, "lyxwritetest"));
215
216         if (tmpfl.empty())
217                 return false;
218
219         unlink(tmpfl);
220         return true;
221 }
222
223
224 #if 0
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,
231                              string const & ext)
232 {
233         FileName real_file;
234         string path_element;
235         bool notfound = true;
236         string tmppath = split(path, path_element, ';');
237
238         while (notfound && !path_element.empty()) {
239                 path_element = os::internal_path(path_element);
240                 if (!suffixIs(path_element, '/'))
241                         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());
246
247                 real_file = fileSearch(path_element, name, ext);
248
249                 if (real_file.empty()) {
250                         do {
251                                 tmppath = split(tmppath, path_element, ';');
252                         } while (!tmppath.empty() && path_element.empty());
253                 } else {
254                         notfound = false;
255                 }
256         }
257         return real_file;
258 }
259 #endif
260
261
262 /// Returns a vector of all files in directory dir having extension ext.
263 vector<FileName> const dirList(FileName const & dir, string const & ext)
264 {
265         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
266         vector<FileName> dirlist;
267
268         string const encoded_dir = dir.toFilesystemEncoding();
269         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
270                 LYXERR(Debug::FILES)
271                         << "Directory \"" << dir
272                         << "\" does not exist to DirList." << endl;
273                 return dirlist;
274         }
275
276         string extension;
277         if (!ext.empty() && ext[0] != '.')
278                 extension += '.';
279         extension += ext;
280
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));
288         }
289         return dirlist;
290 }
291
292
293 // Returns the real name of file name in directory path, with optional
294 // extension ext.
295 FileName const fileSearch(string const & path, string const & name,
296                           string const & ext, search_mode mode)
297 {
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))
304                 return fullname;
305         if (ext.empty())
306                 // We are done.
307                 return mode == allow_unreadable ? fullname : FileName();
308         // Only add the extension if it is not already the extension of
309         // fullname.
310         if (getExtension(fullname.absFilename()) != ext)
311                 fullname = FileName(addExtension(fullname.absFilename(), ext));
312         if (isFileReadable(fullname) || mode == allow_unreadable)
313                 return fullname;
314         return FileName();
315 }
316
317
318 // Search the file name.ext in the subdirectory dir of
319 //   1) user_lyxdir
320 //   2) build_lyxdir (if not empty)
321 //   3) system_lyxdir
322 FileName const libFileSearch(string const & dir, string const & name,
323                            string const & ext)
324 {
325         FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
326                                      name, ext);
327         if (!fullname.empty())
328                 return fullname;
329
330         if (!package().build_support().empty())
331                 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
332                                       name, ext);
333         if (!fullname.empty())
334                 return fullname;
335
336         return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
337 }
338
339
340 FileName const i18nLibFileSearch(string const & dir, string const & name,
341                   string const & ext)
342 {
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.
346
347            Otherwise, w use a trick to guess what gettext has done:
348            each po file is able to tell us its name. (JMarc)
349         */
350
351         string lang = to_ascii(_("[[Replace with the code of your language]]"));
352         string const language = getEnv("LANGUAGE");
353         if (!lang.empty() && !language.empty())
354                 lang = language;
355
356         string l;
357         lang = split(lang, l, ':');
358         while (!l.empty()) {
359                 FileName tmp;
360                 // First try with the full name
361                 tmp = libFileSearch(addPath(dir, l), name, ext);
362                 if (!tmp.empty())
363                         return tmp;
364
365                 // Then the name without country code
366                 string const shortl = token(l, '_', 0);
367                 if (shortl != l) {
368                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
369                         if (!tmp.empty())
370                                 return tmp;
371                 }
372
373 #if 1
374                 // For compatibility, to be removed later (JMarc)
375                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
376                                     ext);
377                 if (!tmp.empty()) {
378                         lyxerr << "i18nLibFileSearch: File `" << tmp
379                                << "' has been found by the old method" <<endl;
380                         return tmp;
381                 }
382 #endif
383                 lang = split(lang, l, ':');
384         }
385
386         return libFileSearch(dir, name, ext);
387 }
388
389
390 string const libScriptSearch(string const & command_in, quote_style style)
391 {
392         static string const token_scriptpath = "$$s/";
393
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)
398                 return command;
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;
405
406         // Does this script file exist?
407         string const script =
408                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
409
410         if (script.empty()) {
411                 // Replace "$$s/" with ""
412                 command.erase(pos1, 4);
413         } else {
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));
417         }
418
419         return command;
420 }
421
422
423 namespace {
424
425 FileName const createTmpDir(FileName const & tempdir, string const & mask)
426 {
427         LYXERR(Debug::FILES)
428                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
429                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
430
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)
436         unlink(tmpfl);
437
438         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
439                 lyxerr << "LyX could not create the temporary directory '"
440                        << tmpfl << "'" << endl;
441                 return FileName();
442         }
443
444         return tmpfl;
445 }
446
447 } // namespace anon
448
449
450 bool destroyDir(FileName const & tmpdir)
451 {
452         try {
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;
456                 return false;
457         }
458 }
459
460
461 string const createBufferTmpDir()
462 {
463         static int count;
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().
467         string const tmpfl =
468                 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
469                 convert<string>(count++);
470
471         if (mkdir(FileName(tmpfl), 0777)) {
472                 lyxerr << "LyX could not create the temporary directory '"
473                        << tmpfl << "'" << endl;
474                 return string();
475         }
476         return tmpfl;
477 }
478
479
480 FileName const createLyXTmpDir(FileName const & deflt)
481 {
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
487                                 // dir inside deflt.
488                                 return createTmpDir(deflt, "lyx_tmpdir");
489                         } else {
490                                 // some other error occured.
491                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
492                         }
493                 } else
494                         return deflt;
495         } else {
496                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
497         }
498 }
499
500
501 bool createDirectory(FileName const & path, int permission)
502 {
503         BOOST_ASSERT(!path.empty());
504         return mkdir(path, permission) == 0;
505 }
506
507
508 // Strip filename from path name
509 string const onlyPath(string const & filename)
510 {
511         // If empty filename, return empty
512         if (filename.empty())
513                 return filename;
514
515         // Find last / or start of filename
516         string::size_type j = filename.rfind('/');
517         return j == string::npos ? "./" : filename.substr(0, j + 1);
518 }
519
520
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)
525 {
526         // checks for already absolute path
527         if (os::is_absolute_path(relPath))
528                 return FileName(relPath);
529
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, "//", "/");
534
535         string tempBase;
536
537         if (os::is_absolute_path(basePath))
538                 tempBase = basePath;
539         else
540                 tempBase = addPath(getcwd().absFilename(), basePath);
541
542         // Handle /./ at the end of the path
543         while (suffixIs(tempBase, "/./"))
544                 tempBase.erase(tempBase.length() - 2);
545
546         // processes relative path
547         string rTemp = tempRel;
548         string temp;
549
550         while (!rTemp.empty()) {
551                 // Split by next /
552                 rTemp = split(rTemp, temp, '/');
553
554                 if (temp == ".") continue;
555                 if (temp == "..") {
556                         // Remove one level of TempBase
557                         string::difference_type i = tempBase.length() - 2;
558                         if (i < 0)
559                                 i = 0;
560                         while (i > 0 && tempBase[i] != '/')
561                                 --i;
562                         if (i > 0)
563                                 tempBase.erase(i, string::npos);
564                         else
565                                 tempBase += '/';
566                 } else if (temp.empty() && !rTemp.empty()) {
567                                 tempBase = os::current_root() + rTemp;
568                                 rTemp.erase();
569                 } else {
570                         // Add this piece to TempBase
571                         if (!suffixIs(tempBase, '/'))
572                                 tempBase += '/';
573                         tempBase += temp;
574                 }
575         }
576
577         // returns absolute path
578         return FileName(os::internal_path(tempBase));
579 }
580
581
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)
586 {
587         string const basename = onlyFilename(fname);
588         string buf;
589
590         if (path != "." && path != "./" && !path.empty()) {
591                 buf = os::internal_path(path);
592                 if (!suffixIs(path, '/'))
593                         buf += '/';
594         }
595
596         return buf + basename;
597 }
598
599
600 // Strips path from filename
601 string const onlyFilename(string const & fname)
602 {
603         if (fname.empty())
604                 return fname;
605
606         string::size_type j = fname.rfind('/');
607         if (j == string::npos) // no '/' in fname
608                 return fname;
609
610         // Strip to basename
611         return fname.substr(j + 1);
612 }
613
614
615 /// Returns true is path is absolute
616 bool absolutePath(string const & path)
617 {
618         return os::is_absolute_path(path);
619 }
620
621
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)
625 {
626         // checks for already absolute path
627         string rTemp = replaceEnvironmentPath(path);
628         if (os::is_absolute_path(rTemp))
629                 return rTemp;
630
631         string temp;
632         string const copy = rTemp;
633
634         // Split by next /
635         rTemp = split(rTemp, temp, '/');
636
637         if (temp == ".")
638                 return getcwd().absFilename() + '/' + rTemp;
639
640         if (temp == "~")
641                 return package().home_dir().absFilename() + '/' + rTemp;
642
643         if (temp == "..")
644                 return makeAbsPath(copy).absFilename();
645
646         // Don't know how to handle this
647         return copy;
648 }
649
650
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)
655 {
656         // Normalize paths like /foo//bar ==> /foo/bar
657         static boost::regex regex("/{2,}");
658         string const tmppath = boost::regex_merge(path, regex, "/");
659
660         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
661
662         if (!npath.is_complete())
663                 return "./" + npath.string() + '/';
664
665         return npath.string() + '/';
666 }
667
668
669 string const getFileContents(FileName const & fname)
670 {
671         string const encodedname = fname.toFilesystemEncoding();
672         if (fs::exists(encodedname)) {
673                 ifstream ifs(encodedname.c_str());
674                 ostringstream ofs;
675                 if (ifs && ofs) {
676                         ofs << ifs.rdbuf();
677                         ifs.close();
678                         return ofs.str();
679                 }
680         }
681         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
682         return string();
683 }
684
685
686 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
687 string const replaceEnvironmentPath(string const & path)
688 {
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]*)\\}";
692
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]*)";
696
697         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
698         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
699         boost::smatch what;
700
701         string result = path;
702         while (1) {
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)
707                                 break;
708                 }
709                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
710         }
711         return result;
712 }
713
714
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.
721 {
722         docstring::size_type const abslen = abspath.length();
723         docstring::size_type const baselen = basepath.length();
724
725         docstring::size_type i = os::common_path(abspath, basepath);
726
727         if (i == 0) {
728                 // actually no match - cannot make it relative
729                 return abspath;
730         }
731
732         // Count how many dirs there are in basepath above match
733         // and append as many '..''s into relpath
734         docstring buf;
735         docstring::size_type j = i;
736         while (j < baselen) {
737                 if (basepath[j] == '/') {
738                         if (j + 1 == baselen)
739                                 break;
740                         buf += "../";
741                 }
742                 ++j;
743         }
744
745         // Append relative stuff from common directory to abspath
746         if (abspath[i] == '/')
747                 ++i;
748         for (; i < abslen; ++i)
749                 buf += abspath[i];
750         // Remove trailing /
751         if (suffixIs(buf, '/'))
752                 buf.erase(buf.length() - 1);
753         // Substitute empty with .
754         if (buf.empty())
755                 buf = '.';
756         return buf;
757 }
758
759
760 // Append sub-directory(ies) to a path in an intelligent way
761 string const addPath(string const & path, string const & path_2)
762 {
763         string buf;
764         string const path2 = os::internal_path(path_2);
765
766         if (!path.empty() && path != "." && path != "./") {
767                 buf = os::internal_path(path);
768                 if (path[path.length() - 1] != '/')
769                         buf += '/';
770         }
771
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);
776                 buf += tmp + '/';
777         }
778         return buf;
779 }
780
781
782 string const changeExtension(string const & oldname, string const & extension)
783 {
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;
788
789         string ext;
790         // Make sure the extension starts with a dot
791         if (!extension.empty() && extension[0] != '.')
792                 ext= '.' + extension;
793         else
794                 ext = extension;
795
796         return os::internal_path(oldname.substr(0, last_dot) + ext);
797 }
798
799
800 string const removeExtension(string const & name)
801 {
802         return changeExtension(name, string());
803 }
804
805
806 string const addExtension(string const & name, string const & extension)
807 {
808         if (!extension.empty() && extension[0] != '.')
809                 return name + '.' + extension;
810         return name + extension;
811 }
812
813
814 /// Return the extension of the file (not including the .)
815 string const getExtension(string const & name)
816 {
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));
823         else
824                 return string();
825 }
826
827
828 // the different filetypes and what they contain in one of the first lines
829 // (dots are any characters).           (Herbert 20020131)
830 // AGR  Grace...
831 // BMP  BM...
832 // EPS  %!PS-Adobe-3.0 EPSF...
833 // FIG  #FIG...
834 // FITS ...BITPIX...
835 // GIF  GIF...
836 // JPG  JFIF
837 // PDF  %PDF-...
838 // PNG  .PNG...
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)
844 // TGIF %TGIF...
845 // TIFF II... or MM...
846 // XBM  ..._bits[]...
847 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
848 //      ...static char *...
849 // XWD  \000\000\000\151        (0x00006900) decimal 105
850 //
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
854
855 string const getFormatFromContents(FileName const & filename)
856 {
857         // paranoia check
858         if (filename.empty() || !isFileReadable(filename))
859                 return string();
860
861         ifstream ifs(filename.toFilesystemEncoding().c_str());
862         if (!ifs)
863                 // Couldn't open file...
864                 return string();
865
866         // gnuzip
867         static string const gzipStamp = "\037\213";
868
869         // PKZIP
870         static string const zipStamp = "PK";
871
872         // compress
873         static string const compressStamp = "\037\235";
874
875         // Maximum strings to read
876         int const max_count = 50;
877         int count = 0;
878
879         string str;
880         string format;
881         bool firstLine = true;
882         while ((count++ < max_count) && format.empty()) {
883                 if (ifs.eof()) {
884                         LYXERR(Debug::GRAPHICS)
885                                 << "filetools(getFormatFromContents)\n"
886                                 << "\tFile type not recognised before EOF!"
887                                 << endl;
888                         break;
889                 }
890
891                 getline(ifs, str);
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)) {
899                                 format =  "gzip";
900
901                         } else if (stamp == zipStamp) {
902                                 format =  "zip";
903
904                         } else if (stamp == compressStamp) {
905                                 format =  "compress";
906
907                         // the graphics part
908                         } else if (stamp == "BM") {
909                                 format =  "bmp";
910
911                         } else if (stamp == "\001\332") {
912                                 format =  "sgi";
913
914                         // PBM family
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') {
918                                 switch (str[1]) {
919                                 case '1':
920                                 case '4':
921                                         format =  "pbm";
922                                     break;
923                                 case '2':
924                                 case '5':
925                                         format =  "pgm";
926                                     break;
927                                 case '3':
928                                 case '6':
929                                         format =  "ppm";
930                                 }
931                                 break;
932
933                         } else if ((stamp == "II") || (stamp == "MM")) {
934                                 format =  "tiff";
935
936                         } else if (prefixIs(str,"%TGIF")) {
937                                 format =  "tgif";
938
939                         } else if (prefixIs(str,"#FIG")) {
940                                 format =  "fig";
941
942                         } else if (prefixIs(str,"GIF")) {
943                                 format =  "gif";
944
945                         } else if (str.size() > 3) {
946                                 int const c = ((str[0] << 24) & (str[1] << 16) &
947                                                (str[2] << 8)  & str[3]);
948                                 if (c == 105) {
949                                         format =  "xwd";
950                                 }
951                         }
952
953                         firstLine = false;
954                 }
955
956                 if (!format.empty())
957                     break;
958                 else if (contains(str,"EPSF"))
959                         // dummy, if we have wrong file description like
960                         // %!PS-Adobe-2.0EPSF"
961                         format =  "eps";
962
963                 else if (contains(str,"Grace"))
964                         format =  "agr";
965
966                 else if (contains(str,"JFIF"))
967                         format =  "jpg";
968
969                 else if (contains(str,"%PDF"))
970                         format =  "pdf";
971
972                 else if (contains(str,"PNG"))
973                         format =  "png";
974
975                 else if (contains(str,"%!PS-Adobe")) {
976                         // eps or ps
977                         ifs >> str;
978                         if (contains(str,"EPSF"))
979                                 format = "eps";
980                         else
981                             format = "ps";
982                 }
983
984                 else if (contains(str,"_bits[]"))
985                         format = "xbm";
986
987                 else if (contains(str,"XPM") || contains(str, "static char *"))
988                         format = "xpm";
989
990                 else if (contains(str,"BITPIX"))
991                         format = "fits";
992         }
993
994         if (!format.empty()) {
995                 LYXERR(Debug::GRAPHICS)
996                         << "Recognised Fileformat: " << format << endl;
997                 return format;
998         }
999
1000         LYXERR(Debug::GRAPHICS)
1001                 << "filetools(getFormatFromContents)\n"
1002                 << "\tCouldn't find a known format!\n";
1003         return string();
1004 }
1005
1006
1007 /// check for zipped file
1008 bool zippedFile(FileName const & name)
1009 {
1010         string const type = getFormatFromContents(name);
1011         if (contains("gzip zip compress", type) && !type.empty())
1012                 return true;
1013         return false;
1014 }
1015
1016
1017 string const unzippedFileName(string const & zipped_file)
1018 {
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;
1023 }
1024
1025
1026 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1027 {
1028         FileName const tempfile = FileName(unzipped_file.empty() ?
1029                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1030                 unzipped_file);
1031         // Run gunzip
1032         string const command = "gunzip -c " +
1033                 zipped_file.toFilesystemEncoding() + " > " +
1034                 tempfile.toFilesystemEncoding();
1035         Systemcall one;
1036         one.startscript(Systemcall::Wait, command);
1037         // test that command was executed successfully (anon)
1038         // yes, please do. (Lgb)
1039         return tempfile;
1040 }
1041
1042
1043 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1044 {
1045         string str = path;
1046
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()) + "]");
1051
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, "~");
1056
1057         if (str.length() <= threshold)
1058                 return from_utf8(os::external_path(str));
1059
1060         string const prefix = ".../";
1061         string temp;
1062
1063         while (str.length() > threshold)
1064                 str = split(str, temp, '/');
1065
1066         // Did we shorten everything away?
1067         if (str.empty()) {
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);
1072
1073                 string::size_type len = str.length();
1074                 string const tail =
1075                         str.substr(len - threshold / 2 - 2, len - 1);
1076                 str = head + "..." + tail;
1077         }
1078
1079         return from_utf8(os::external_path(prefix + str));
1080 }
1081
1082
1083 bool readLink(FileName const & file, FileName & link)
1084 {
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);
1091         if (nRead <= 0)
1092                 return false;
1093         linkbuffer[nRead] = '\0'; // terminator
1094         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1095         return true;
1096 #else
1097         return false;
1098 #endif
1099 }
1100
1101
1102 cmd_ret const runCommand(string const & cmd)
1103 {
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).
1107
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
1113
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());
1118 #else
1119 #error No popen() function.
1120 #endif
1121
1122         // (Claus Hentschel) Check if popen was succesful ;-)
1123         if (!inf) {
1124                 lyxerr << "RunCommand:: could not start child process" << endl;
1125                 return make_pair(-1, string());
1126         }
1127
1128         string ret;
1129         int c = fgetc(inf);
1130         while (c != EOF) {
1131                 ret += static_cast<char>(c);
1132                 c = fgetc(inf);
1133         }
1134
1135 #if defined (HAVE_PCLOSE)
1136         int const pret = pclose(inf);
1137 #elif defined (HAVE__PCLOSE)
1138         int const pret = _pclose(inf);
1139 #else
1140 #error No pclose() function.
1141 #endif
1142
1143         if (pret == -1)
1144                 perror("RunCommand:: could not terminate child process");
1145
1146         return make_pair(pret, ret);
1147 }
1148
1149
1150 FileName const findtexfile(string const & fil, string const & /*format*/)
1151 {
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.
1158            Lgb
1159         */
1160
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()))
1165                 return absfile;
1166
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
1173         // is used."
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
1179         // bst - BSTINPUTS
1180         // graphic/figure - TEXPICTS, TEXINPUTS
1181         // ist - TEXINDEXSTYLE, INDEXSTYLE
1182         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1183         // tex - TEXINPUTS
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;
1188
1189         cmd_ret const c = runCommand(kpsecmd);
1190
1191         LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1192                  << "kpse result = `" << rtrim(c.second, "\n\r")
1193                  << '\'' << endl;
1194         if (c.first != -1)
1195                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1196                                                         "\n\r")));
1197         else
1198                 return FileName();
1199 }
1200
1201
1202 void removeAutosaveFile(string const & filename)
1203 {
1204         string a = onlyPath(filename);
1205         a += '#';
1206         a += onlyFilename(filename);
1207         a += '#';
1208         FileName const autosave(a);
1209         if (fs::exists(autosave.toFilesystemEncoding()))
1210                 unlink(autosave);
1211 }
1212
1213
1214 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1215         string const & message)
1216 {
1217         LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1218                 << message << std::endl;
1219         // FIXME: Why is this func deleting a file? (Lgb)
1220         if (zipped)
1221                 unlink(file);
1222 }
1223
1224
1225 string const readBB_from_PSFile(FileName const & file)
1226 {
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_);
1237
1238         if (format != "eps" && format != "ps") {
1239                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1240                 return string();
1241         }
1242
1243         static boost::regex bbox_re(
1244                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1245         std::ifstream is(file_.toFilesystemEncoding().c_str());
1246         while (is) {
1247                 string s;
1248                 getline(is,s);
1249                 boost::smatch what;
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
1254                         // sensible
1255                         ostringstream os;
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);
1260                         return bb;
1261                 }
1262         }
1263         readBB_lyxerrMessage(file_, zipped, "no bb found");
1264         return string();
1265 }
1266
1267
1268 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1269 {
1270         // If the original is newer than the copy, then copy the original
1271         // to the new directory.
1272
1273         string const file1 = filename1.toFilesystemEncoding();
1274         string const file2 = filename2.toFilesystemEncoding();
1275         int cmp = 0;
1276         if (fs::exists(file1) && fs::exists(file2)) {
1277                 double const tmp = difftime(fs::last_write_time(file1),
1278                                             fs::last_write_time(file2));
1279                 if (tmp != 0)
1280                         cmp = tmp > 0 ? 1 : -1;
1281
1282         } else if (fs::exists(file1)) {
1283                 cmp = 1;
1284         } else if (fs::exists(file2)) {
1285                 cmp = -1;
1286         }
1287
1288         return cmp;
1289 }
1290
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
1295
1296 #ifdef WIN32
1297 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1298 {
1299         int ret = 0;
1300         {
1301                 FILETIME ftLocal;
1302                 HANDLE hFind;
1303                 WIN32_FIND_DATA  ff32;
1304
1305                 hFind = FindFirstFile(f,&ff32);
1306                 if (hFind != INVALID_HANDLE_VALUE)
1307                 {
1308                         FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1309                         FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1310                         FindClose(hFind);
1311                         ret = 1;
1312                 }
1313         }
1314         return ret;
1315 }
1316
1317 #else
1318 #ifdef unix
1319 uLong filetime(const char * f, tm_zip * tmzip, uLong * /*dt*/)
1320 {
1321         int ret=0;
1322         struct stat s;                                                            /* results of stat() */
1323         struct tm* filedate;
1324         time_t tm_t=0;
1325
1326         if (strcmp(f,"-")!=0) {
1327                 char name[MAXFILENAME+1];
1328                 int len = strlen(f);
1329                 if (len > MAXFILENAME)
1330                         len = MAXFILENAME;
1331
1332                 strncpy(name, f,MAXFILENAME-1);
1333                 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1334                 name[ MAXFILENAME ] = '\0';
1335
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) {
1340                         tm_t = s.st_mtime;
1341                         ret = 1;
1342                 }
1343         }
1344         filedate = localtime(&tm_t);
1345
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;
1352
1353         return ret;
1354 }
1355
1356 #else
1357
1358 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1359 {
1360         return 0;
1361 }
1362 #endif
1363 #endif
1364
1365 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1366 {
1367         int err = 0;
1368         zipFile zf;
1369         int errclose;
1370         void * buf = NULL;
1371
1372         int size_buf = WRITEBUFFERSIZE;
1373         buf = (void*)malloc(size_buf);
1374         if (buf==NULL) {
1375                 lyxerr << "Error allocating memory" << endl;
1376                 return false;
1377         }
1378         string const zfile = zipfile.toFilesystemEncoding();
1379         const char * fname = zfile.c_str();
1380
1381 #ifdef USEWIN32IOAPI
1382         zlib_filefunc_def ffunc;
1383         fill_win32_filefunc(&ffunc);
1384         // false: not append
1385         zf = zipOpen2(fname, false, NULL, &ffunc);
1386 #else
1387         zf = zipOpen(fname, false);
1388 #endif
1389
1390         if (zf == NULL) {
1391                 lyxerr << "error opening " << zipfile << endl;
1392                 return false;
1393         }
1394
1395         for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1396                 FILE * fin;
1397                 int size_read;
1398                 zip_fileinfo zi;
1399                 const char * diskfilename = it->first.c_str();
1400                 const char * filenameinzip = it->second.c_str();
1401                 unsigned long crcFile=0;
1402
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;
1405                 zi.dosDate = 0;
1406                 zi.internal_fa = 0;
1407                 zi.external_fa = 0;
1408                 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1409
1410                 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1411                         NULL,0,NULL,0,NULL /* comment*/,
1412                         Z_DEFLATED,
1413                         Z_DEFAULT_COMPRESSION,            // compression level
1414                         0,
1415                 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1416                         -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1417                         NULL,                                             // password,
1418                         crcFile);
1419
1420                 if (err != ZIP_OK) {
1421                         lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1422                         return false;
1423                 }
1424                 fin = fopen(diskfilename, "rb");
1425                 if (fin==NULL) {
1426                         lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1427                         return false;
1428                 }
1429
1430                 do {
1431                         err = ZIP_OK;
1432                         size_read = (int)fread(buf, 1, size_buf, fin);
1433                         if (size_read < size_buf)
1434                                 if (feof(fin)==0) {
1435                                         lyxerr << "error in reading " << filenameinzip << endl;
1436                                         return false;
1437                                 }
1438
1439                         if (size_read>0) {
1440                                 err = zipWriteInFileInZip (zf, buf, size_read);
1441                                 if (err<0) {
1442                                         lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1443                                         return false;
1444                                 }
1445                         }
1446                 } while ((err == ZIP_OK) && (size_read>0));
1447
1448                 if (fin)
1449                         fclose(fin);
1450
1451                 err = zipCloseFileInZip(zf);
1452                 if (err != ZIP_OK) {
1453                         lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1454                         return false;
1455                 }
1456         }
1457         errclose = zipClose(zf, NULL);
1458         if (errclose != ZIP_OK) {
1459                 lyxerr << "error in closing " << zipfile << endl;
1460                 return false;
1461         }
1462         free(buf);
1463         return true;
1464 }
1465
1466 // adapted from miniunz.c
1467
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)
1473 {
1474 #ifdef WIN32
1475         HANDLE hFile;
1476         FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1477
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);
1484         CloseHandle(hFile);
1485 #else
1486 #ifdef unix
1487         utimbuf ut;
1488         tm newdate;
1489
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;
1497         else
1498                 newdate.tm_year=tmu_date.tm_year ;
1499         newdate.tm_isdst=-1;
1500
1501         ut.actime=ut.modtime=mktime(&newdate);
1502         utime(filename,&ut);
1503 #endif
1504 #endif
1505 }
1506
1507
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)
1513 {
1514         char filename_inzip[256];
1515         char* filename_withoutpath;
1516         char* p;
1517         int err=UNZ_OK;
1518         FILE *fout=NULL;
1519         void* buf;
1520         uInt size_buf;
1521
1522         unz_file_info file_info;
1523         //uLong ratio=0;
1524         err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1525
1526         if (err!=UNZ_OK) {
1527                 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1528                 return err;
1529         }
1530
1531         size_buf = WRITEBUFFERSIZE;
1532         buf = (void*)malloc(size_buf);
1533         if (buf==NULL) {
1534                 lyxerr << "Error allocating memory" << endl;
1535                 return UNZ_INTERNALERROR;
1536         }
1537
1538         p = filename_withoutpath = filename_inzip;
1539         while ((*p) != '\0') {
1540                 if (((*p)=='/') || ((*p)=='\\'))
1541                         filename_withoutpath = p+1;
1542                 p++;
1543         }
1544         // this is a directory
1545         if ((*filename_withoutpath)=='\0') {
1546                 if ((*popt_extract_without_path)==0)
1547                         makedir(filename_inzip);
1548         }
1549         // this is a filename
1550         else {
1551                 char write_filename[1024];
1552
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, "/");
1558
1559                 if ((*popt_extract_without_path)==0)
1560                         strcat(write_filename, filename_inzip);
1561                 else
1562                         strcat(write_filename, filename_withoutpath);
1563
1564                 err = unzOpenCurrentFilePassword(uf,password);
1565                 if (err!=UNZ_OK) {
1566                         lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1567                 } else {
1568                         fout=fopen(write_filename, "wb");
1569
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");
1578                         }
1579
1580                         if (fout==NULL) {
1581                                 lyxerr << "error opening " << write_filename << endl;
1582                         }
1583                 }
1584
1585                 if (fout!=NULL) {
1586                         LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1587
1588                         do {
1589                                 err = unzReadCurrentFile(uf,buf,size_buf);
1590                                 if (err<0) {
1591                                         lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1592                                         break;
1593                                 }
1594                                 if (err>0)
1595                                         if (fwrite(buf,err,1,fout)!=1) {
1596                                                 lyxerr << "error in writing extracted file" << endl;
1597                                                 err=UNZ_ERRNO;
1598                                                 break;
1599                                         }
1600                         } while (err>0);
1601                         if (fout)
1602                                 fclose(fout);
1603
1604                         if (err==0)
1605                                 change_file_date(write_filename,file_info.dosDate,
1606                                         file_info.tmu_date);
1607                 }
1608
1609                 if (err==UNZ_OK) {
1610                         err = unzCloseCurrentFile (uf);
1611                         if (err!=UNZ_OK) {
1612                                 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1613                         }
1614                 }
1615                 else
1616                         unzCloseCurrentFile(uf);          /* don't lose the error */
1617         }
1618
1619         free(buf);
1620         return err;
1621 }
1622
1623
1624 bool unzipToDir(string const & zipfile, string const & dirname)
1625 {
1626         unzFile uf=NULL;
1627 #ifdef USEWIN32IOAPI
1628         zlib_filefunc_def ffunc;
1629 #endif
1630
1631         const char * zipfilename = zipfile.c_str();
1632
1633 #ifdef USEWIN32IOAPI
1634         fill_win32_filefunc(&ffunc);
1635         uf = unzOpen2(zipfilename, &ffunc);
1636 #else
1637         uf = unzOpen(zipfilename);
1638 #endif
1639
1640         if (uf==NULL) {
1641                 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1642                 return false;
1643         }
1644
1645         uLong i;
1646         unz_global_info gi;
1647         int err;
1648         //FILE* fout=NULL;
1649         int opt_extract_without_path = 0;
1650         int opt_overwrite = 1;
1651         char * password = NULL;
1652
1653         err = unzGetGlobalInfo (uf, &gi);
1654         if (err != UNZ_OK) {
1655                 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1656                 return false;
1657         }
1658
1659         for (i=0; i < gi.number_entry; i++) {
1660                 if (do_extract_currentfile(uf, &opt_extract_without_path,
1661                         &opt_overwrite,
1662                         password, dirname.c_str()) != UNZ_OK)
1663                         break;
1664
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;;
1669                                 break;
1670                         }
1671                 }
1672         }
1673
1674         unzCloseCurrentFile(uf);
1675     return true;
1676 }
1677
1678 } //namespace support
1679 } // namespace lyx