]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Fix file inclusion in src/support/filetools.cpp under mac
[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 #ifdef HAVE_UNISTD_H
54 # include <unistd.h>
55 #endif
56 #ifdef HAVE_DIRECT_H
57 # include <direct.h>
58 #endif
59 #ifdef HAVE_SYS_TYPES_H
60 # include <sys/types.h>
61 #endif
62 #ifdef HAVE_SYS_STAT_H
63 # include <sys/stat.h>
64 #endif
65 #ifdef HAVE_IO_H
66 # include <io.h>
67 #endif
68 #ifdef HAVE_SYS_UTIME_H
69 # include <sys/utime.h>
70 #endif
71 #ifdef HAVE_UTIME_H
72 # include <utime.h>
73 #endif
74
75 #include "zip.h"
76 #include "unzip.h"
77
78 #ifdef WIN32
79 #define USEWIN32IOAPI
80 #include "iowin32.h"
81 #endif
82
83 #define WRITEBUFFERSIZE (16384)
84 #define MAXFILENAME (256)
85
86
87 #ifndef CXX_GLOBAL_CSTD
88 using std::fgetc;
89 #endif
90
91 using std::endl;
92 using std::getline;
93 using std::make_pair;
94 using std::string;
95 using std::ifstream;
96 using std::ostringstream;
97 using std::vector;
98 using std::pair;
99
100 namespace fs = boost::filesystem;
101
102 namespace lyx {
103 namespace support {
104
105 bool isLyXFilename(string const & filename)
106 {
107         return suffixIs(ascii_lowercase(filename), ".lyx");
108 }
109
110
111 bool isSGMLFilename(string const & filename)
112 {
113         return suffixIs(ascii_lowercase(filename), ".sgml");
114 }
115
116
117 bool isValidLaTeXFilename(string const & filename)
118 {
119         string const invalid_chars("#$%{}()[]\"^");
120         if (filename.find_first_of(invalid_chars) != string::npos)
121                 return false;
122         else
123                 return true;
124 }
125
126
127 string const latex_path(string const & original_path,
128                 latex_path_extension extension,
129                 latex_path_dots dots)
130 {
131         // On cygwin, we may need windows or posix style paths.
132         string path = os::latex_path(original_path);
133         path = subst(path, "~", "\\string~");
134         if (path.find(' ') != string::npos) {
135                 // We can't use '"' because " is sometimes active (e.g. if
136                 // babel is loaded with the "german" option)
137                 if (extension == EXCLUDE_EXTENSION) {
138                         // ChangeExtension calls os::internal_path internally
139                         // so don't use it to remove the extension.
140                         string const ext = getExtension(path);
141                         string const base = ext.empty() ?
142                                 path :
143                                 path.substr(0, path.length() - ext.length() - 1);
144                         // ChangeExtension calls os::internal_path internally
145                         // so don't use it to re-add the extension.
146                         path = "\\string\"" + base + "\\string\"." + ext;
147                 } else {
148                         path = "\\string\"" + path + "\\string\"";
149                 }
150         }
151
152         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
153 }
154
155
156 // Substitutes spaces with underscores in filename (and path)
157 string const makeLatexName(string const & file)
158 {
159         string name = onlyFilename(file);
160         string const path = onlyPath(file);
161
162         // ok so we scan through the string twice, but who cares.
163         string const keep = "abcdefghijklmnopqrstuvwxyz"
164                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
165                 "@!'()*+,-./0123456789:;<=>?[]`|";
166
167         string::size_type pos = 0;
168         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
169                 name[pos++] = '_';
170
171         return addName(path, name);
172 }
173
174
175 string const quoteName(string const & name, quote_style style)
176 {
177         switch(style) {
178         case quote_shell:
179                 // This does not work for filenames containing " (windows)
180                 // or ' (all other OSes). This can't be changed easily, since
181                 // we would need to adapt the command line parser in
182                 // Forkedcall::generateChild. Therefore we don't pass user
183                 // filenames to child processes if possible. We store them in
184                 // a python script instead, where we don't have these
185                 // limitations.
186                 return (os::shell() == os::UNIX) ?
187                         '\'' + name + '\'':
188                         '"' + name + '"';
189         case quote_python:
190                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
191                      + "\"";
192         }
193         // shut up stupid compiler
194         return string();
195 }
196
197
198 bool isFileReadable(FileName const & filename)
199 {
200         std::string const path = filename.toFilesystemEncoding();
201         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
202 }
203
204
205 //returns true: dir writeable
206 //        false: not writeable
207 bool isDirWriteable(FileName const & path)
208 {
209         LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
210
211         FileName const tmpfl(tempName(path, "lyxwritetest"));
212
213         if (tmpfl.empty())
214                 return false;
215
216         unlink(tmpfl);
217         return true;
218 }
219
220
221 #if 0
222 // Uses a string of paths separated by ";"s to find a file to open.
223 // Can't cope with pathnames with a ';' in them. Returns full path to file.
224 // If path entry begins with $$LyX/, use system_lyxdir
225 // If path entry begins with $$User/, use user_lyxdir
226 // Example: "$$User/doc;$$LyX/doc"
227 FileName const fileOpenSearch(string const & path, string const & name,
228                              string const & ext)
229 {
230         FileName real_file;
231         string path_element;
232         bool notfound = true;
233         string tmppath = split(path, path_element, ';');
234
235         while (notfound && !path_element.empty()) {
236                 path_element = os::internal_path(path_element);
237                 if (!suffixIs(path_element, '/'))
238                         path_element += '/';
239                 path_element = subst(path_element, "$$LyX",
240                                      package().system_support().absFilename());
241                 path_element = subst(path_element, "$$User",
242                                      package().user_support().absFilename());
243
244                 real_file = fileSearch(path_element, name, ext);
245
246                 if (real_file.empty()) {
247                         do {
248                                 tmppath = split(tmppath, path_element, ';');
249                         } while (!tmppath.empty() && path_element.empty());
250                 } else {
251                         notfound = false;
252                 }
253         }
254         return real_file;
255 }
256 #endif
257
258
259 /// Returns a vector of all files in directory dir having extension ext.
260 vector<FileName> const dirList(FileName const & dir, string const & ext)
261 {
262         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
263         vector<FileName> dirlist;
264
265         string const encoded_dir = dir.toFilesystemEncoding();
266         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
267                 LYXERR(Debug::FILES)
268                         << "Directory \"" << dir
269                         << "\" does not exist to DirList." << endl;
270                 return dirlist;
271         }
272
273         string extension;
274         if (!ext.empty() && ext[0] != '.')
275                 extension += '.';
276         extension += ext;
277
278         fs::directory_iterator dit(encoded_dir);
279         fs::directory_iterator end;
280         for (; dit != end; ++dit) {
281                 string const & fil = dit->leaf();
282                 if (suffixIs(fil, extension))
283                         dirlist.push_back(FileName::fromFilesystemEncoding(
284                                         encoded_dir + '/' + fil));
285         }
286         return dirlist;
287 }
288
289
290 // Returns the real name of file name in directory path, with optional
291 // extension ext.
292 FileName const fileSearch(string const & path, string const & name,
293                           string const & ext, search_mode mode)
294 {
295         // if `name' is an absolute path, we ignore the setting of `path'
296         // Expand Environmentvariables in 'name'
297         string const tmpname = replaceEnvironmentPath(name);
298         FileName fullname(makeAbsPath(tmpname, path));
299         // search first without extension, then with it.
300         if (isFileReadable(fullname))
301                 return fullname;
302         if (ext.empty())
303                 // We are done.
304                 return mode == allow_unreadable ? fullname : FileName();
305         // Only add the extension if it is not already the extension of
306         // fullname.
307         if (getExtension(fullname.absFilename()) != ext)
308                 fullname = FileName(addExtension(fullname.absFilename(), ext));
309         if (isFileReadable(fullname) || mode == allow_unreadable)
310                 return fullname;
311         return FileName();
312 }
313
314
315 // Search the file name.ext in the subdirectory dir of
316 //   1) user_lyxdir
317 //   2) build_lyxdir (if not empty)
318 //   3) system_lyxdir
319 FileName const libFileSearch(string const & dir, string const & name,
320                            string const & ext)
321 {
322         FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
323                                      name, ext);
324         if (!fullname.empty())
325                 return fullname;
326
327         if (!package().build_support().empty())
328                 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
329                                       name, ext);
330         if (!fullname.empty())
331                 return fullname;
332
333         return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
334 }
335
336
337 FileName const i18nLibFileSearch(string const & dir, string const & name,
338                   string const & ext)
339 {
340         /* The highest priority value is the `LANGUAGE' environment
341            variable. But we don't use the value if the currently
342            selected locale is the C locale. This is a GNU extension.
343
344            Otherwise, w use a trick to guess what gettext has done:
345            each po file is able to tell us its name. (JMarc)
346         */
347
348         string lang = to_ascii(_("[[Replace with the code of your language]]"));
349         string const language = getEnv("LANGUAGE");
350         if (!lang.empty() && !language.empty())
351                 lang = language;
352
353         string l;
354         lang = split(lang, l, ':');
355         while (!l.empty()) {
356                 FileName tmp;
357                 // First try with the full name
358                 tmp = libFileSearch(addPath(dir, l), name, ext);
359                 if (!tmp.empty())
360                         return tmp;
361
362                 // Then the name without country code
363                 string const shortl = token(l, '_', 0);
364                 if (shortl != l) {
365                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
366                         if (!tmp.empty())
367                                 return tmp;
368                 }
369
370 #if 1
371                 // For compatibility, to be removed later (JMarc)
372                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
373                                     ext);
374                 if (!tmp.empty()) {
375                         lyxerr << "i18nLibFileSearch: File `" << tmp
376                                << "' has been found by the old method" <<endl;
377                         return tmp;
378                 }
379 #endif
380                 lang = split(lang, l, ':');
381         }
382
383         return libFileSearch(dir, name, ext);
384 }
385
386
387 string const libScriptSearch(string const & command_in, quote_style style)
388 {
389         static string const token_scriptpath = "$$s/";
390
391         string command = command_in;
392         // Find the starting position of "$$s/"
393         string::size_type const pos1 = command.find(token_scriptpath);
394         if (pos1 == string::npos)
395                 return command;
396         // Find the end of the "$$s/some_subdir/some_script" word within
397         // command. Assumes that the script name does not contain spaces.
398         string::size_type const start_script = pos1 + 4;
399         string::size_type const pos2 = command.find(' ', start_script);
400         string::size_type const size_script = pos2 == string::npos?
401                 (command.size() - start_script) : pos2 - start_script;
402
403         // Does this script file exist?
404         string const script =
405                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
406
407         if (script.empty()) {
408                 // Replace "$$s/" with ""
409                 command.erase(pos1, 4);
410         } else {
411                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
412                 string::size_type const size_replace = size_script + 4;
413                 command.replace(pos1, size_replace, quoteName(script, style));
414         }
415
416         return command;
417 }
418
419
420 namespace {
421
422 FileName const createTmpDir(FileName const & tempdir, string const & mask)
423 {
424         LYXERR(Debug::FILES)
425                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
426                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
427
428         FileName const tmpfl(tempName(tempdir, mask));
429         // lyx::tempName actually creates a file to make sure that it
430         // stays unique. So we have to delete it before we can create
431         // a dir with the same name. Note also that we are not thread
432         // safe because of the gap between unlink and mkdir. (Lgb)
433         unlink(tmpfl);
434
435         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
436                 lyxerr << "LyX could not create the temporary directory '"
437                        << tmpfl << "'" << endl;
438                 return FileName();
439         }
440
441         return tmpfl;
442 }
443
444 } // namespace anon
445
446
447 bool destroyDir(FileName const & tmpdir)
448 {
449         try {
450                 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
451         } catch (fs::filesystem_error const & fe){
452                 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
453                 return false;
454         }
455 }
456
457
458 string const createBufferTmpDir()
459 {
460         static int count;
461         // We are in our own directory.  Why bother to mangle name?
462         // In fact I wrote this code to circumvent a problematic behaviour
463         // (bug?) of EMX mkstemp().
464         string const tmpfl =
465                 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
466                 convert<string>(count++);
467
468         if (mkdir(FileName(tmpfl), 0777)) {
469                 lyxerr << "LyX could not create the temporary directory '"
470                        << tmpfl << "'" << endl;
471                 return string();
472         }
473         return tmpfl;
474 }
475
476
477 FileName const createLyXTmpDir(FileName const & deflt)
478 {
479         if (!deflt.empty() && deflt.absFilename() != "/tmp") {
480                 if (mkdir(deflt, 0777)) {
481                         if (isDirWriteable(deflt)) {
482                                 // deflt could not be created because it
483                                 // did exist already, so let's create our own
484                                 // dir inside deflt.
485                                 return createTmpDir(deflt, "lyx_tmpdir");
486                         } else {
487                                 // some other error occured.
488                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
489                         }
490                 } else
491                         return deflt;
492         } else {
493                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
494         }
495 }
496
497
498 bool createDirectory(FileName const & path, int permission)
499 {
500         BOOST_ASSERT(!path.empty());
501         return mkdir(path, permission) == 0;
502 }
503
504
505 // Strip filename from path name
506 string const onlyPath(string const & filename)
507 {
508         // If empty filename, return empty
509         if (filename.empty())
510                 return filename;
511
512         // Find last / or start of filename
513         string::size_type j = filename.rfind('/');
514         return j == string::npos ? "./" : filename.substr(0, j + 1);
515 }
516
517
518 // Convert relative path into absolute path based on a basepath.
519 // If relpath is absolute, just use that.
520 // If basepath is empty, use CWD as base.
521 FileName const makeAbsPath(string const & relPath, string const & basePath)
522 {
523         // checks for already absolute path
524         if (os::is_absolute_path(relPath))
525                 return FileName(relPath);
526
527         // Copies given paths
528         string tempRel = os::internal_path(relPath);
529         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
530         tempRel = subst(tempRel, "//", "/");
531
532         string tempBase;
533
534         if (os::is_absolute_path(basePath))
535                 tempBase = basePath;
536         else
537                 tempBase = addPath(getcwd().absFilename(), basePath);
538
539         // Handle /./ at the end of the path
540         while (suffixIs(tempBase, "/./"))
541                 tempBase.erase(tempBase.length() - 2);
542
543         // processes relative path
544         string rTemp = tempRel;
545         string temp;
546
547         while (!rTemp.empty()) {
548                 // Split by next /
549                 rTemp = split(rTemp, temp, '/');
550
551                 if (temp == ".") continue;
552                 if (temp == "..") {
553                         // Remove one level of TempBase
554                         string::difference_type i = tempBase.length() - 2;
555                         if (i < 0)
556                                 i = 0;
557                         while (i > 0 && tempBase[i] != '/')
558                                 --i;
559                         if (i > 0)
560                                 tempBase.erase(i, string::npos);
561                         else
562                                 tempBase += '/';
563                 } else if (temp.empty() && !rTemp.empty()) {
564                                 tempBase = os::current_root() + rTemp;
565                                 rTemp.erase();
566                 } else {
567                         // Add this piece to TempBase
568                         if (!suffixIs(tempBase, '/'))
569                                 tempBase += '/';
570                         tempBase += temp;
571                 }
572         }
573
574         // returns absolute path
575         return FileName(os::internal_path(tempBase));
576 }
577
578
579 // Correctly append filename to the pathname.
580 // If pathname is '.', then don't use pathname.
581 // Chops any path of filename.
582 string const addName(string const & path, string const & fname)
583 {
584         string const basename = onlyFilename(fname);
585         string buf;
586
587         if (path != "." && path != "./" && !path.empty()) {
588                 buf = os::internal_path(path);
589                 if (!suffixIs(path, '/'))
590                         buf += '/';
591         }
592
593         return buf + basename;
594 }
595
596
597 // Strips path from filename
598 string const onlyFilename(string const & fname)
599 {
600         if (fname.empty())
601                 return fname;
602
603         string::size_type j = fname.rfind('/');
604         if (j == string::npos) // no '/' in fname
605                 return fname;
606
607         // Strip to basename
608         return fname.substr(j + 1);
609 }
610
611
612 /// Returns true is path is absolute
613 bool absolutePath(string const & path)
614 {
615         return os::is_absolute_path(path);
616 }
617
618
619 // Create absolute path. If impossible, don't do anything
620 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
621 string const expandPath(string const & path)
622 {
623         // checks for already absolute path
624         string rTemp = replaceEnvironmentPath(path);
625         if (os::is_absolute_path(rTemp))
626                 return rTemp;
627
628         string temp;
629         string const copy = rTemp;
630
631         // Split by next /
632         rTemp = split(rTemp, temp, '/');
633
634         if (temp == ".")
635                 return getcwd().absFilename() + '/' + rTemp;
636
637         if (temp == "~")
638                 return package().home_dir().absFilename() + '/' + rTemp;
639
640         if (temp == "..")
641                 return makeAbsPath(copy).absFilename();
642
643         // Don't know how to handle this
644         return copy;
645 }
646
647
648 // Normalize a path. Constracts path/../path
649 // Can't handle "../../" or "/../" (Asger)
650 // Also converts paths like /foo//bar ==> /foo/bar
651 string const normalizePath(string const & path)
652 {
653         // Normalize paths like /foo//bar ==> /foo/bar
654         static boost::regex regex("/{2,}");
655         string const tmppath = boost::regex_merge(path, regex, "/");
656
657         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
658
659         if (!npath.is_complete())
660                 return "./" + npath.string() + '/';
661
662         return npath.string() + '/';
663 }
664
665
666 string const getFileContents(FileName const & fname)
667 {
668         string const encodedname = fname.toFilesystemEncoding();
669         if (fs::exists(encodedname)) {
670                 ifstream ifs(encodedname.c_str());
671                 ostringstream ofs;
672                 if (ifs && ofs) {
673                         ofs << ifs.rdbuf();
674                         ifs.close();
675                         return ofs.str();
676                 }
677         }
678         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
679         return string();
680 }
681
682
683 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
684 string const replaceEnvironmentPath(string const & path)
685 {
686         // ${VAR} is defined as
687         // $\{[A-Za-z_][A-Za-z_0-9]*\}
688         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
689
690         // $VAR is defined as:
691         // $\{[A-Za-z_][A-Za-z_0-9]*\}
692         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
693
694         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
695         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
696         boost::smatch what;
697
698         string result = path;
699         while (1) {
700                 regex_match(result, what, envvar_br_re);
701                 if (!what[0].matched) {
702                         regex_match(result, what, envvar_re);
703                         if (!what[0].matched)
704                                 break;
705                 }
706                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
707         }
708         return result;
709 }
710
711
712 // Make relative path out of two absolute paths
713 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
714 // Makes relative path out of absolute path. If it is deeper than basepath,
715 // it's easy. If basepath and abspath share something (they are all deeper
716 // than some directory), it'll be rendered using ..'s. If they are completely
717 // different, then the absolute path will be used as relative path.
718 {
719         docstring::size_type const abslen = abspath.length();
720         docstring::size_type const baselen = basepath.length();
721
722         docstring::size_type i = os::common_path(abspath, basepath);
723
724         if (i == 0) {
725                 // actually no match - cannot make it relative
726                 return abspath;
727         }
728
729         // Count how many dirs there are in basepath above match
730         // and append as many '..''s into relpath
731         docstring buf;
732         docstring::size_type j = i;
733         while (j < baselen) {
734                 if (basepath[j] == '/') {
735                         if (j + 1 == baselen)
736                                 break;
737                         buf += "../";
738                 }
739                 ++j;
740         }
741
742         // Append relative stuff from common directory to abspath
743         if (abspath[i] == '/')
744                 ++i;
745         for (; i < abslen; ++i)
746                 buf += abspath[i];
747         // Remove trailing /
748         if (suffixIs(buf, '/'))
749                 buf.erase(buf.length() - 1);
750         // Substitute empty with .
751         if (buf.empty())
752                 buf = '.';
753         return buf;
754 }
755
756
757 // Append sub-directory(ies) to a path in an intelligent way
758 string const addPath(string const & path, string const & path_2)
759 {
760         string buf;
761         string const path2 = os::internal_path(path_2);
762
763         if (!path.empty() && path != "." && path != "./") {
764                 buf = os::internal_path(path);
765                 if (path[path.length() - 1] != '/')
766                         buf += '/';
767         }
768
769         if (!path2.empty()) {
770                 string::size_type const p2start = path2.find_first_not_of('/');
771                 string::size_type const p2end = path2.find_last_not_of('/');
772                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
773                 buf += tmp + '/';
774         }
775         return buf;
776 }
777
778
779 string const changeExtension(string const & oldname, string const & extension)
780 {
781         string::size_type const last_slash = oldname.rfind('/');
782         string::size_type last_dot = oldname.rfind('.');
783         if (last_dot < last_slash && last_slash != string::npos)
784                 last_dot = string::npos;
785
786         string ext;
787         // Make sure the extension starts with a dot
788         if (!extension.empty() && extension[0] != '.')
789                 ext= '.' + extension;
790         else
791                 ext = extension;
792
793         return os::internal_path(oldname.substr(0, last_dot) + ext);
794 }
795
796
797 string const removeExtension(string const & name)
798 {
799         return changeExtension(name, string());
800 }
801
802
803 string const addExtension(string const & name, string const & extension)
804 {
805         if (!extension.empty() && extension[0] != '.')
806                 return name + '.' + extension;
807         return name + extension;
808 }
809
810
811 /// Return the extension of the file (not including the .)
812 string const getExtension(string const & name)
813 {
814         string::size_type const last_slash = name.rfind('/');
815         string::size_type const last_dot = name.rfind('.');
816         if (last_dot != string::npos &&
817             (last_slash == string::npos || last_dot > last_slash))
818                 return name.substr(last_dot + 1,
819                                    name.length() - (last_dot + 1));
820         else
821                 return string();
822 }
823
824
825 // the different filetypes and what they contain in one of the first lines
826 // (dots are any characters).           (Herbert 20020131)
827 // AGR  Grace...
828 // BMP  BM...
829 // EPS  %!PS-Adobe-3.0 EPSF...
830 // FIG  #FIG...
831 // FITS ...BITPIX...
832 // GIF  GIF...
833 // JPG  JFIF
834 // PDF  %PDF-...
835 // PNG  .PNG...
836 // PBM  P1... or P4     (B/W)
837 // PGM  P2... or P5     (Grayscale)
838 // PPM  P3... or P6     (color)
839 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
840 // SGI  \001\332...     (decimal 474)
841 // TGIF %TGIF...
842 // TIFF II... or MM...
843 // XBM  ..._bits[]...
844 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
845 //      ...static char *...
846 // XWD  \000\000\000\151        (0x00006900) decimal 105
847 //
848 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
849 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
850 // Z    \037\235                UNIX compress
851
852 string const getFormatFromContents(FileName const & filename)
853 {
854         // paranoia check
855         if (filename.empty() || !isFileReadable(filename))
856                 return string();
857
858         ifstream ifs(filename.toFilesystemEncoding().c_str());
859         if (!ifs)
860                 // Couldn't open file...
861                 return string();
862
863         // gnuzip
864         static string const gzipStamp = "\037\213";
865
866         // PKZIP
867         static string const zipStamp = "PK";
868
869         // compress
870         static string const compressStamp = "\037\235";
871
872         // Maximum strings to read
873         int const max_count = 50;
874         int count = 0;
875
876         string str;
877         string format;
878         bool firstLine = true;
879         while ((count++ < max_count) && format.empty()) {
880                 if (ifs.eof()) {
881                         LYXERR(Debug::GRAPHICS)
882                                 << "filetools(getFormatFromContents)\n"
883                                 << "\tFile type not recognised before EOF!"
884                                 << endl;
885                         break;
886                 }
887
888                 getline(ifs, str);
889                 string const stamp = str.substr(0, 2);
890                 if (firstLine && str.size() >= 2) {
891                         // at first we check for a zipped file, because this
892                         // information is saved in the first bytes of the file!
893                         // also some graphic formats which save the information
894                         // in the first line, too.
895                         if (prefixIs(str, gzipStamp)) {
896                                 format =  "gzip";
897
898                         } else if (stamp == zipStamp) {
899                                 format =  "zip";
900
901                         } else if (stamp == compressStamp) {
902                                 format =  "compress";
903
904                         // the graphics part
905                         } else if (stamp == "BM") {
906                                 format =  "bmp";
907
908                         } else if (stamp == "\001\332") {
909                                 format =  "sgi";
910
911                         // PBM family
912                         // Don't need to use str.at(0), str.at(1) because
913                         // we already know that str.size() >= 2
914                         } else if (str[0] == 'P') {
915                                 switch (str[1]) {
916                                 case '1':
917                                 case '4':
918                                         format =  "pbm";
919                                     break;
920                                 case '2':
921                                 case '5':
922                                         format =  "pgm";
923                                     break;
924                                 case '3':
925                                 case '6':
926                                         format =  "ppm";
927                                 }
928                                 break;
929
930                         } else if ((stamp == "II") || (stamp == "MM")) {
931                                 format =  "tiff";
932
933                         } else if (prefixIs(str,"%TGIF")) {
934                                 format =  "tgif";
935
936                         } else if (prefixIs(str,"#FIG")) {
937                                 format =  "fig";
938
939                         } else if (prefixIs(str,"GIF")) {
940                                 format =  "gif";
941
942                         } else if (str.size() > 3) {
943                                 int const c = ((str[0] << 24) & (str[1] << 16) &
944                                                (str[2] << 8)  & str[3]);
945                                 if (c == 105) {
946                                         format =  "xwd";
947                                 }
948                         }
949
950                         firstLine = false;
951                 }
952
953                 if (!format.empty())
954                     break;
955                 else if (contains(str,"EPSF"))
956                         // dummy, if we have wrong file description like
957                         // %!PS-Adobe-2.0EPSF"
958                         format =  "eps";
959
960                 else if (contains(str,"Grace"))
961                         format =  "agr";
962
963                 else if (contains(str,"JFIF"))
964                         format =  "jpg";
965
966                 else if (contains(str,"%PDF"))
967                         format =  "pdf";
968
969                 else if (contains(str,"PNG"))
970                         format =  "png";
971
972                 else if (contains(str,"%!PS-Adobe")) {
973                         // eps or ps
974                         ifs >> str;
975                         if (contains(str,"EPSF"))
976                                 format = "eps";
977                         else
978                             format = "ps";
979                 }
980
981                 else if (contains(str,"_bits[]"))
982                         format = "xbm";
983
984                 else if (contains(str,"XPM") || contains(str, "static char *"))
985                         format = "xpm";
986
987                 else if (contains(str,"BITPIX"))
988                         format = "fits";
989         }
990
991         if (!format.empty()) {
992                 LYXERR(Debug::GRAPHICS)
993                         << "Recognised Fileformat: " << format << endl;
994                 return format;
995         }
996
997         LYXERR(Debug::GRAPHICS)
998                 << "filetools(getFormatFromContents)\n"
999                 << "\tCouldn't find a known format!\n";
1000         return string();
1001 }
1002
1003
1004 /// check for zipped file
1005 bool zippedFile(FileName const & name)
1006 {
1007         string const type = getFormatFromContents(name);
1008         if (contains("gzip zip compress", type) && !type.empty())
1009                 return true;
1010         return false;
1011 }
1012
1013
1014 string const unzippedFileName(string const & zipped_file)
1015 {
1016         string const ext = getExtension(zipped_file);
1017         if (ext == "gz" || ext == "z" || ext == "Z")
1018                 return changeExtension(zipped_file, string());
1019         return "unzipped_" + zipped_file;
1020 }
1021
1022
1023 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1024 {
1025         FileName const tempfile = FileName(unzipped_file.empty() ?
1026                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1027                 unzipped_file);
1028         // Run gunzip
1029         string const command = "gunzip -c " +
1030                 zipped_file.toFilesystemEncoding() + " > " +
1031                 tempfile.toFilesystemEncoding();
1032         Systemcall one;
1033         one.startscript(Systemcall::Wait, command);
1034         // test that command was executed successfully (anon)
1035         // yes, please do. (Lgb)
1036         return tempfile;
1037 }
1038
1039
1040 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1041 {
1042         string str = path;
1043
1044         // If file is from LyXDir, display it as if it were relative.
1045         string const system = package().system_support().absFilename();
1046         if (prefixIs(str, system) && str != system)
1047                 return from_utf8("[" + str.erase(0, system.length()) + "]");
1048
1049         // replace /home/blah with ~/
1050         string const home = package().home_dir().absFilename();
1051         if (!home.empty() && prefixIs(str, home))
1052                 str = subst(str, home, "~");
1053
1054         if (str.length() <= threshold)
1055                 return from_utf8(os::external_path(str));
1056
1057         string const prefix = ".../";
1058         string temp;
1059
1060         while (str.length() > threshold)
1061                 str = split(str, temp, '/');
1062
1063         // Did we shorten everything away?
1064         if (str.empty()) {
1065                 // Yes, filename itself is too long.
1066                 // Pick the start and the end of the filename.
1067                 str = onlyFilename(path);
1068                 string const head = str.substr(0, threshold / 2 - 3);
1069
1070                 string::size_type len = str.length();
1071                 string const tail =
1072                         str.substr(len - threshold / 2 - 2, len - 1);
1073                 str = head + "..." + tail;
1074         }
1075
1076         return from_utf8(os::external_path(prefix + str));
1077 }
1078
1079
1080 bool readLink(FileName const & file, FileName & link)
1081 {
1082 #ifdef HAVE_READLINK
1083         char linkbuffer[512];
1084         // Should be PATH_MAX but that needs autconf support
1085         string const encoded = file.toFilesystemEncoding();
1086         int const nRead = ::readlink(encoded.c_str(),
1087                                      linkbuffer, sizeof(linkbuffer) - 1);
1088         if (nRead <= 0)
1089                 return false;
1090         linkbuffer[nRead] = '\0'; // terminator
1091         link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1092         return true;
1093 #else
1094         return false;
1095 #endif
1096 }
1097
1098
1099 cmd_ret const runCommand(string const & cmd)
1100 {
1101         // FIXME: replace all calls to RunCommand with ForkedCall
1102         // (if the output is not needed) or the code in ISpell.cpp
1103         // (if the output is needed).
1104
1105         // One question is if we should use popen or
1106         // create our own popen based on fork, exec, pipe
1107         // of course the best would be to have a
1108         // pstream (process stream), with the
1109         // variants ipstream, opstream
1110
1111 #if defined (HAVE_POPEN)
1112         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1113 #elif defined (HAVE__POPEN)
1114         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1115 #else
1116 #error No popen() function.
1117 #endif
1118
1119         // (Claus Hentschel) Check if popen was succesful ;-)
1120         if (!inf) {
1121                 lyxerr << "RunCommand:: could not start child process" << endl;
1122                 return make_pair(-1, string());
1123         }
1124
1125         string ret;
1126         int c = fgetc(inf);
1127         while (c != EOF) {
1128                 ret += static_cast<char>(c);
1129                 c = fgetc(inf);
1130         }
1131
1132 #if defined (HAVE_PCLOSE)
1133         int const pret = pclose(inf);
1134 #elif defined (HAVE__PCLOSE)
1135         int const pret = _pclose(inf);
1136 #else
1137 #error No pclose() function.
1138 #endif
1139
1140         if (pret == -1)
1141                 perror("RunCommand:: could not terminate child process");
1142
1143         return make_pair(pret, ret);
1144 }
1145
1146
1147 FileName const findtexfile(string const & fil, string const & /*format*/)
1148 {
1149         /* There is no problem to extend this function too use other
1150            methods to look for files. It could be setup to look
1151            in environment paths and also if wanted as a last resort
1152            to a recursive find. One of the easier extensions would
1153            perhaps be to use the LyX file lookup methods. But! I am
1154            going to implement this until I see some demand for it.
1155            Lgb
1156         */
1157
1158         // If the file can be found directly, we just return a
1159         // absolute path version of it.
1160         FileName const absfile(makeAbsPath(fil));
1161         if (fs::exists(absfile.toFilesystemEncoding()))
1162                 return absfile;
1163
1164         // No we try to find it using kpsewhich.
1165         // It seems from the kpsewhich manual page that it is safe to use
1166         // kpsewhich without --format: "When the --format option is not
1167         // given, the search path used when looking for a file is inferred
1168         // from the name given, by looking for a known extension. If no
1169         // known extension is found, the search path for TeX source files
1170         // is used."
1171         // However, we want to take advantage of the format sine almost all
1172         // the different formats has environment variables that can be used
1173         // to controll which paths to search. f.ex. bib looks in
1174         // BIBINPUTS and TEXBIB. Small list follows:
1175         // bib - BIBINPUTS, TEXBIB
1176         // bst - BSTINPUTS
1177         // graphic/figure - TEXPICTS, TEXINPUTS
1178         // ist - TEXINDEXSTYLE, INDEXSTYLE
1179         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1180         // tex - TEXINPUTS
1181         // tfm - TFMFONTS, TEXFONTS
1182         // This means that to use kpsewhich in the best possible way we
1183         // should help it by setting additional path in the approp. envir.var.
1184         string const kpsecmd = "kpsewhich " + fil;
1185
1186         cmd_ret const c = runCommand(kpsecmd);
1187
1188         LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1189                  << "kpse result = `" << rtrim(c.second, "\n\r")
1190                  << '\'' << endl;
1191         if (c.first != -1)
1192                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1193                                                         "\n\r")));
1194         else
1195                 return FileName();
1196 }
1197
1198
1199 void removeAutosaveFile(string const & filename)
1200 {
1201         string a = onlyPath(filename);
1202         a += '#';
1203         a += onlyFilename(filename);
1204         a += '#';
1205         FileName const autosave(a);
1206         if (fs::exists(autosave.toFilesystemEncoding()))
1207                 unlink(autosave);
1208 }
1209
1210
1211 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1212         string const & message)
1213 {
1214         LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1215                 << message << std::endl;
1216         // FIXME: Why is this func deleting a file? (Lgb)
1217         if (zipped)
1218                 unlink(file);
1219 }
1220
1221
1222 string const readBB_from_PSFile(FileName const & file)
1223 {
1224         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1225         // It seems that every command in the header has an own line,
1226         // getline() should work for all files.
1227         // On the other hand some plot programs write the bb at the
1228         // end of the file. Than we have in the header:
1229         // %%BoundingBox: (atend)
1230         // In this case we must check the end.
1231         bool zipped = zippedFile(file);
1232         FileName const file_ = zipped ? unzipFile(file) : file;
1233         string const format = getFormatFromContents(file_);
1234
1235         if (format != "eps" && format != "ps") {
1236                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1237                 return string();
1238         }
1239
1240         static boost::regex bbox_re(
1241                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1242         std::ifstream is(file_.toFilesystemEncoding().c_str());
1243         while (is) {
1244                 string s;
1245                 getline(is,s);
1246                 boost::smatch what;
1247                 if (regex_match(s, what, bbox_re)) {
1248                         // Our callers expect the tokens in the string
1249                         // separated by single spaces.
1250                         // FIXME: change return type from string to something
1251                         // sensible
1252                         ostringstream os;
1253                         os << what.str(1) << ' ' << what.str(2) << ' '
1254                            << what.str(3) << ' ' << what.str(4);
1255                         string const bb = os.str();
1256                         readBB_lyxerrMessage(file_, zipped, bb);
1257                         return bb;
1258                 }
1259         }
1260         readBB_lyxerrMessage(file_, zipped, "no bb found");
1261         return string();
1262 }
1263
1264
1265 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1266 {
1267         // If the original is newer than the copy, then copy the original
1268         // to the new directory.
1269
1270         string const file1 = filename1.toFilesystemEncoding();
1271         string const file2 = filename2.toFilesystemEncoding();
1272         int cmp = 0;
1273         if (fs::exists(file1) && fs::exists(file2)) {
1274                 double const tmp = difftime(fs::last_write_time(file1),
1275                                             fs::last_write_time(file2));
1276                 if (tmp != 0)
1277                         cmp = tmp > 0 ? 1 : -1;
1278
1279         } else if (fs::exists(file1)) {
1280                 cmp = 1;
1281         } else if (fs::exists(file2)) {
1282                 cmp = -1;
1283         }
1284
1285         return cmp;
1286 }
1287
1288 // the following is adapted from zlib-1.2.3/contrib/minizip.c
1289 // and miniunz.c, except that
1290 // 1. mkdir, makedir is replaced by lyx' own version
1291 // 2. printf is replaced by lyxerr
1292
1293 #ifdef WIN32
1294 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1295 {
1296         int ret = 0;
1297         {
1298                 FILETIME ftLocal;
1299                 HANDLE hFind;
1300                 WIN32_FIND_DATA  ff32;
1301
1302                 hFind = FindFirstFile(f,&ff32);
1303                 if (hFind != INVALID_HANDLE_VALUE)
1304                 {
1305                         FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1306                         FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1307                         FindClose(hFind);
1308                         ret = 1;
1309                 }
1310         }
1311         return ret;
1312 }
1313
1314 #else
1315 #ifdef unix
1316 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1317 {
1318         int ret=0;
1319         struct stat s;                                                            /* results of stat() */
1320         struct tm* filedate;
1321         time_t tm_t=0;
1322
1323         if (strcmp(f,"-")!=0) {
1324                 char name[MAXFILENAME+1];
1325                 int len = strlen(f);
1326                 if (len > MAXFILENAME)
1327                         len = MAXFILENAME;
1328
1329                 strncpy(name, f,MAXFILENAME-1);
1330                 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1331                 name[ MAXFILENAME ] = '\0';
1332
1333                 if (name[len - 1] == '/')
1334                         name[len - 1] = '\0';
1335                 /* not all systems allow stat'ing a file with / appended */
1336                 if (stat(name,&s)==0) {
1337                         tm_t = s.st_mtime;
1338                         ret = 1;
1339                 }
1340         }
1341         filedate = localtime(&tm_t);
1342
1343         tmzip->tm_sec  = filedate->tm_sec;
1344         tmzip->tm_min  = filedate->tm_min;
1345         tmzip->tm_hour = filedate->tm_hour;
1346         tmzip->tm_mday = filedate->tm_mday;
1347         tmzip->tm_mon  = filedate->tm_mon ;
1348         tmzip->tm_year = filedate->tm_year;
1349
1350         return ret;
1351 }
1352
1353 #else
1354
1355 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1356 {
1357         return 0;
1358 }
1359 #endif
1360 #endif
1361
1362 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1363 {
1364         int err = 0;
1365         zipFile zf;
1366         int errclose;
1367         void * buf = NULL;
1368
1369         int size_buf = WRITEBUFFERSIZE;
1370         buf = (void*)malloc(size_buf);
1371         if (buf==NULL) {
1372                 lyxerr << "Error allocating memory" << endl;
1373                 return false;
1374         }
1375         string const zfile = zipfile.toFilesystemEncoding();
1376         const char * fname = zfile.c_str();
1377
1378 #ifdef USEWIN32IOAPI
1379         zlib_filefunc_def ffunc;
1380         fill_win32_filefunc(&ffunc);
1381         // false: not append
1382         zf = zipOpen2(fname, false, NULL, &ffunc);
1383 #else
1384         zf = zipOpen(fname, false);
1385 #endif
1386
1387         if (zf == NULL) {
1388                 lyxerr << "error opening " << zipfile << endl;
1389                 return false;
1390         }
1391
1392         for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1393                 FILE * fin;
1394                 int size_read;
1395                 zip_fileinfo zi;
1396                 const char * diskfilename = it->first.c_str();
1397                 const char * filenameinzip = it->second.c_str();
1398                 unsigned long crcFile=0;
1399
1400                 zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
1401                         zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
1402                 zi.dosDate = 0;
1403                 zi.internal_fa = 0;
1404                 zi.external_fa = 0;
1405                 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1406
1407                 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1408                         NULL,0,NULL,0,NULL /* comment*/,
1409                         Z_DEFLATED,
1410                         Z_DEFAULT_COMPRESSION,            // compression level
1411                         0,
1412                 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1413                         -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1414                         NULL,                                             // password,
1415                         crcFile);
1416
1417                 if (err != ZIP_OK) {
1418                         lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1419                         return false;
1420                 }
1421                 fin = fopen(diskfilename, "rb");
1422                 if (fin==NULL) {
1423                         lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1424                         return false;
1425                 }
1426
1427                 do {
1428                         err = ZIP_OK;
1429                         size_read = (int)fread(buf, 1, size_buf, fin);
1430                         if (size_read < size_buf)
1431                                 if (feof(fin)==0) {
1432                                         lyxerr << "error in reading " << filenameinzip << endl;
1433                                         return false;
1434                                 }
1435
1436                         if (size_read>0) {
1437                                 err = zipWriteInFileInZip (zf, buf, size_read);
1438                                 if (err<0) {
1439                                         lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1440                                         return false;
1441                                 }
1442                         }
1443                 } while ((err == ZIP_OK) && (size_read>0));
1444
1445                 if (fin)
1446                         fclose(fin);
1447
1448                 err = zipCloseFileInZip(zf);
1449                 if (err != ZIP_OK) {
1450                         lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1451                         return false;
1452                 }
1453         }
1454         errclose = zipClose(zf, NULL);
1455         if (errclose != ZIP_OK) {
1456                 lyxerr << "error in closing " << zipfile << endl;
1457                 return false;
1458         }
1459         free(buf);
1460         return true;
1461 }
1462
1463 // adapted from miniunz.c
1464
1465 /* change_file_date : change the date/time of a file
1466         filename : the filename of the file where date/time must be modified
1467         dosdate : the new date at the MSDos format (4 bytes)
1468         tmu_date : the SAME new date at the tm_unz format */
1469 void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date)
1470 {
1471 #ifdef WIN32
1472         HANDLE hFile;
1473         FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1474
1475         hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE,
1476                 0,NULL,OPEN_EXISTING,0,NULL);
1477         GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
1478         DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
1479         LocalFileTimeToFileTime(&ftLocal,&ftm);
1480         SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
1481         CloseHandle(hFile);
1482 #else
1483 #ifdef unix
1484         struct utimbuf ut;
1485         struct tm newdate;
1486
1487         newdate.tm_sec = tmu_date.tm_sec;
1488         newdate.tm_min=tmu_date.tm_min;
1489         newdate.tm_hour=tmu_date.tm_hour;
1490         newdate.tm_mday=tmu_date.tm_mday;
1491         newdate.tm_mon=tmu_date.tm_mon;
1492         if (tmu_date.tm_year > 1900)
1493                 newdate.tm_year=tmu_date.tm_year - 1900;
1494         else
1495                 newdate.tm_year=tmu_date.tm_year ;
1496         newdate.tm_isdst=-1;
1497
1498         ut.actime=ut.modtime=mktime(&newdate);
1499         utime(filename,&ut);
1500 #endif
1501 #endif
1502 }
1503
1504
1505 int do_extract_currentfile(unzFile uf,
1506         const int * popt_extract_without_path,
1507         int * popt_overwrite,
1508         const char * password,
1509         const char * dirname)
1510 {
1511         char filename_inzip[256];
1512         char* filename_withoutpath;
1513         char* p;
1514         int err=UNZ_OK;
1515         FILE *fout=NULL;
1516         void* buf;
1517         uInt size_buf;
1518
1519         unz_file_info file_info;
1520         //uLong ratio=0;
1521         err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1522
1523         if (err!=UNZ_OK) {
1524                 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1525                 return err;
1526         }
1527
1528         size_buf = WRITEBUFFERSIZE;
1529         buf = (void*)malloc(size_buf);
1530         if (buf==NULL) {
1531                 lyxerr << "Error allocating memory" << endl;
1532                 return UNZ_INTERNALERROR;
1533         }
1534
1535         p = filename_withoutpath = filename_inzip;
1536         while ((*p) != '\0') {
1537                 if (((*p)=='/') || ((*p)=='\\'))
1538                         filename_withoutpath = p+1;
1539                 p++;
1540         }
1541         // this is a directory
1542         if ((*filename_withoutpath)=='\0') {
1543                 if ((*popt_extract_without_path)==0)
1544                         makedir(filename_inzip);
1545         }
1546         // this is a filename
1547         else {
1548                 char write_filename[1024];
1549
1550                 strcpy(write_filename, dirname);
1551                 int len = strlen(write_filename);
1552                 if (write_filename[len-1] != '\\' &&
1553                         write_filename[len-1] != '/')
1554                         strcat(write_filename, "/");
1555
1556                 if ((*popt_extract_without_path)==0)
1557                         strcat(write_filename, filename_inzip);
1558                 else
1559                         strcat(write_filename, filename_withoutpath);
1560
1561                 err = unzOpenCurrentFilePassword(uf,password);
1562                 if (err!=UNZ_OK) {
1563                         lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1564                 } else {
1565                         fout=fopen(write_filename, "wb");
1566
1567                         /* some zipfile don't contain directory alone before file */
1568                         if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
1569                                 (filename_withoutpath!=(char*)filename_inzip)) {
1570                                 char c=*(filename_withoutpath-1);
1571                                 *(filename_withoutpath-1)='\0';
1572                                 makedir(write_filename);
1573                                 *(filename_withoutpath-1)=c;
1574                                 fout=fopen(write_filename,"wb");
1575                         }
1576
1577                         if (fout==NULL) {
1578                                 lyxerr << "error opening " << write_filename << endl;
1579                         }
1580                 }
1581
1582                 if (fout!=NULL) {
1583                         LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1584
1585                         do {
1586                                 err = unzReadCurrentFile(uf,buf,size_buf);
1587                                 if (err<0) {
1588                                         lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1589                                         break;
1590                                 }
1591                                 if (err>0)
1592                                         if (fwrite(buf,err,1,fout)!=1) {
1593                                                 lyxerr << "error in writing extracted file" << endl;
1594                                                 err=UNZ_ERRNO;
1595                                                 break;
1596                                         }
1597                         } while (err>0);
1598                         if (fout)
1599                                 fclose(fout);
1600
1601                         if (err==0)
1602                                 change_file_date(write_filename,file_info.dosDate,
1603                                         file_info.tmu_date);
1604                 }
1605
1606                 if (err==UNZ_OK) {
1607                         err = unzCloseCurrentFile (uf);
1608                         if (err!=UNZ_OK) {
1609                                 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1610                         }
1611                 }
1612                 else
1613                         unzCloseCurrentFile(uf);          /* don't lose the error */
1614         }
1615
1616         free(buf);
1617         return err;
1618 }
1619
1620
1621 bool unzipToDir(string const & zipfile, string const & dirname)
1622 {
1623         unzFile uf=NULL;
1624 #ifdef USEWIN32IOAPI
1625         zlib_filefunc_def ffunc;
1626 #endif
1627
1628         const char * zipfilename = zipfile.c_str();
1629
1630 #ifdef USEWIN32IOAPI
1631         fill_win32_filefunc(&ffunc);
1632         uf = unzOpen2(zipfilename, &ffunc);
1633 #else
1634         uf = unzOpen(zipfilename);
1635 #endif
1636
1637         if (uf==NULL) {
1638                 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1639                 return false;
1640         }
1641
1642         uLong i;
1643         unz_global_info gi;
1644         int err;
1645         //FILE* fout=NULL;
1646         int opt_extract_without_path = 0;
1647         int opt_overwrite = 1;
1648         char * password = NULL;
1649
1650         err = unzGetGlobalInfo (uf, &gi);
1651         if (err != UNZ_OK) {
1652                 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1653                 return false;
1654         }
1655
1656         for (i=0; i < gi.number_entry; i++) {
1657                 if (do_extract_currentfile(uf, &opt_extract_without_path,
1658                         &opt_overwrite,
1659                         password, dirname.c_str()) != UNZ_OK)
1660                         break;
1661
1662                 if ((i+1)<gi.number_entry) {
1663                         err = unzGoToNextFile(uf);
1664                         if (err != UNZ_OK) {
1665                                 lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;;
1666                                 break;
1667                         }
1668                 }
1669         }
1670
1671         unzCloseCurrentFile(uf);
1672     return true;
1673 }
1674
1675 } //namespace support
1676 } // namespace lyx