]> git.lyx.org Git - lyx.git/blob - src/support/FileName.cpp
6883b7bda1f9f14ecd5d9360a71e882a008980be
[lyx.git] / src / support / FileName.cpp
1 /**
2  * \file FileName.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "support/FileName.h"
14 #include "support/FileNameList.h"
15
16 #include "support/convert.h"
17 #include "support/debug.h"
18 #include "support/filetools.h"
19 #include "support/lassert.h"
20 #include "support/lstrings.h"
21 #include "support/qstring_helpers.h"
22 #include "support/os.h"
23 #include "support/Package.h"
24 #include "support/qstring_helpers.h"
25
26 #include <QDateTime>
27 #include <QDir>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QList>
31 #include <QTemporaryFile>
32 #include <QTime>
33
34 #include <boost/crc.hpp>
35 #include <boost/scoped_array.hpp>
36
37 #include <algorithm>
38 #include <iterator>
39 #include <fstream>
40 #include <iomanip>
41 #include <map>
42 #include <sstream>
43
44 #ifdef HAVE_SYS_TYPES_H
45 # include <sys/types.h>
46 #endif
47 #ifdef HAVE_SYS_STAT_H
48 # include <sys/stat.h>
49 #endif
50 #ifdef HAVE_UNISTD_H
51 # include <unistd.h>
52 #endif
53 #ifdef HAVE_DIRECT_H
54 # include <direct.h>
55 #endif
56 #ifdef _WIN32
57 # include <windows.h>
58 #endif
59
60 #include <cerrno>
61 #include <fcntl.h>
62
63 #if defined(HAVE_MKSTEMP) && ! defined(HAVE_DECL_MKSTEMP)
64 extern "C" int mkstemp(char *);
65 #endif
66
67 #if !defined(HAVE_MKSTEMP) && defined(HAVE_MKTEMP)
68 # ifdef HAVE_IO_H
69 #  include <io.h>
70 # endif
71 # ifdef HAVE_PROCESS_H
72 #  include <process.h>
73 # endif
74 #endif
75
76 // Three implementations of checksum(), depending on having mmap support or not.
77 #if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
78 #define SUM_WITH_MMAP
79 #include <sys/mman.h>
80 #endif // SUM_WITH_MMAP
81
82 using namespace std;
83
84 // OK, this is ugly, but it is the only workaround I found to compile
85 // with gcc (any version) on a system which uses a non-GNU toolchain.
86 // The problem is that gcc uses a weak symbol for a particular
87 // instantiation and that the system linker usually does not
88 // understand those weak symbols (seen on HP-UX, tru64, AIX and
89 // others). Thus we force an explicit instanciation of this particular
90 // template (JMarc)
91 template struct boost::detail::crc_table_t<32, 0x04C11DB7, true>;
92
93 namespace lyx {
94 namespace support {
95
96 /////////////////////////////////////////////////////////////////////
97 //
98 // FileName::Private
99 //
100 /////////////////////////////////////////////////////////////////////
101
102 struct FileName::Private
103 {
104         Private() {}
105
106         Private(string const & abs_filename) : fi(toqstr(abs_filename))
107         {
108                 fi.setCaching(fi.exists() ? true : false);
109         }
110         ///
111         inline void refresh() 
112         {
113 // There seems to be a bug in Qt >= 4.2.0, at least, that causes problems with
114 // QFileInfo::refresh() on *nix. So we recreate the object in that case.
115 // FIXME: When Trolltech fixes the bug, we will have to replace 0x999999 below
116 // with the actual working minimum version.
117 #if defined(_WIN32) || (QT_VERSION >= 0x999999)
118                 fi.refresh();
119 #else
120                 fi = QFileInfo(fi.absoluteFilePath());
121 #endif
122         }
123
124         unsigned long checksum();
125
126         ///
127         QFileInfo fi;
128 };
129
130 unsigned long FileName::Private::checksum()
131 {
132 #if QT_VERSION >= 0x999999
133         // First version of checksum uses Qt4.4 mmap support.
134         // FIXME: This code is not ready with Qt4.4.2,
135         // see http://bugzilla.lyx.org/show_bug.cgi?id=5293
136         // FIXME: should we check if the MapExtension extension is supported?
137         // see QAbstractFileEngine::supportsExtension() and 
138         // QAbstractFileEngine::MapExtension)
139         QFile qf(fi.filePath());
140         if (!qf.open(QIODevice::ReadOnly))
141                 return 0;
142         qint64 size = fi.size();
143         uchar * ubeg = qf.map(0, size);
144         uchar * uend = ubeg + size;
145         boost::crc_32_type ucrc;
146         ucrc.process_block(ubeg, uend);
147         qf.unmap(ubeg);
148         qf.close();
149         return ucrc.checksum();
150 #endif
151
152         //FIXME: This doesn't work on Windows for non ascii file names. Provided that
153         // Windows package uses Qt4.4, this isn't a problem.
154         QByteArray const encoded = QFile::encodeName(fi.absoluteFilePath());
155         char const * file = encoded.constData();
156
157 #ifdef SUM_WITH_MMAP
158         //LYXERR(Debug::FILES, "using mmap (lightning fast)");
159
160         int fd = open(file, O_RDONLY);
161         if (!fd)
162                 return 0;
163
164         struct stat info;
165         fstat(fd, &info);
166
167         void * mm = mmap(0, info.st_size, PROT_READ,
168                          MAP_PRIVATE, fd, 0);
169         // Some platforms have the wrong type for MAP_FAILED (compaq cxx).
170         if (mm == reinterpret_cast<void*>(MAP_FAILED)) {
171                 close(fd);
172                 return 0;
173         }
174
175         char * beg = static_cast<char*>(mm);
176         char * end = beg + info.st_size;
177
178         boost::crc_32_type crc;
179         crc.process_block(beg, end);
180         unsigned long result = crc.checksum();
181
182         munmap(mm, info.st_size);
183         close(fd);
184
185         return result;
186
187 #else // no SUM_WITH_MMAP
188
189         //LYXERR(Debug::FILES, "lyx::sum() using istreambuf_iterator (fast)");
190         ifstream ifs(file, ios_base::in | ios_base::binary);
191         if (!ifs)
192                 return 0;
193
194         istreambuf_iterator<char> beg(ifs);
195         istreambuf_iterator<char> end;
196         boost::crc_32_type crc;
197         crc = for_each(beg, end, crc);
198         return crc.checksum();
199
200 #endif // SUM_WITH_MMAP
201 }
202
203 /////////////////////////////////////////////////////////////////////
204 //
205 // FileName
206 //
207 /////////////////////////////////////////////////////////////////////
208
209
210 FileName::FileName() : d(new Private)
211 {
212 }
213
214
215 FileName::FileName(string const & abs_filename)
216         : d(abs_filename.empty() ? new Private : new Private(abs_filename))
217 {
218 }
219
220
221 FileName::~FileName()
222 {
223         delete d;
224 }
225
226
227 FileName::FileName(FileName const & rhs) : d(new Private)
228 {
229         d->fi = rhs.d->fi;
230 }
231
232
233 FileName::FileName(FileName const & rhs, string const & suffix) : d(new Private)
234 {
235         set(rhs, suffix);
236 }
237
238
239 FileName & FileName::operator=(FileName const & rhs)
240 {
241         d->fi = rhs.d->fi;
242         return *this;
243 }
244
245
246 bool FileName::empty() const
247 {
248         return d->fi.absoluteFilePath().isEmpty();
249 }
250
251
252 bool FileName::isAbsolute() const
253 {
254         return d->fi.isAbsolute();
255 }
256
257
258 string FileName::absFilename() const
259 {
260         return fromqstr(d->fi.absoluteFilePath());
261 }
262
263
264 void FileName::set(string const & name)
265 {
266         d->fi.setFile(toqstr(name));
267 }
268
269
270 void FileName::set(FileName const & rhs, string const & suffix)
271 {
272         if (!rhs.d->fi.isDir())
273                 d->fi.setFile(rhs.d->fi.filePath() + toqstr(suffix));
274         else
275                 d->fi.setFile(QDir(rhs.d->fi.absoluteFilePath()), toqstr(suffix));
276 }
277
278
279 void FileName::erase()
280 {
281         d->fi = QFileInfo();
282 }
283
284
285 bool FileName::copyTo(FileName const & name) const
286 {
287         LYXERR(Debug::FILES, "Copying " << name);
288         QFile::remove(name.d->fi.absoluteFilePath());
289         bool success = QFile::copy(d->fi.absoluteFilePath(), name.d->fi.absoluteFilePath());
290         if (!success)
291                 LYXERR0("FileName::copyTo(): Could not copy file "
292                         << *this << " to " << name);
293         return success;
294 }
295
296
297 bool FileName::renameTo(FileName const & name) const
298 {
299         bool success = QFile::rename(d->fi.absoluteFilePath(), name.d->fi.absoluteFilePath());
300         if (!success)
301                 LYXERR0("Could not rename file " << *this << " to " << name);
302         return success;
303 }
304
305
306 bool FileName::moveTo(FileName const & name) const
307 {
308         QFile::remove(name.d->fi.absoluteFilePath());
309
310         bool success = QFile::rename(d->fi.absoluteFilePath(),
311                 name.d->fi.absoluteFilePath());
312         if (!success)
313                 LYXERR0("Could not move file " << *this << " to " << name);
314         return success;
315 }
316
317
318 bool FileName::changePermission(unsigned long int mode) const
319 {
320 #if defined (HAVE_CHMOD) && defined (HAVE_MODE_T)
321         if (::chmod(toFilesystemEncoding().c_str(), mode_t(mode)) != 0) {
322                 LYXERR0("File " << *this << ": cannot change permission to "
323                         << mode << ".");
324                 return false;
325         }
326 #endif
327         return true;
328 }
329
330
331 string FileName::toFilesystemEncoding() const
332 {
333         QByteArray const encoded = QFile::encodeName(d->fi.absoluteFilePath());
334         return string(encoded.begin(), encoded.end());
335 }
336
337
338 FileName FileName::fromFilesystemEncoding(string const & name)
339 {
340         QByteArray const encoded(name.c_str(), name.length());
341         return FileName(fromqstr(QFile::decodeName(encoded)));
342 }
343
344
345 bool FileName::exists() const
346 {
347         return d->fi.exists();
348 }
349
350
351 bool FileName::isSymLink() const
352 {
353         return d->fi.isSymLink();
354 }
355
356
357 bool FileName::isFileEmpty() const
358 {
359         return d->fi.size() == 0;
360 }
361
362
363 bool FileName::isDirectory() const
364 {
365         return d->fi.isDir();
366 }
367
368
369 bool FileName::isReadOnly() const
370 {
371         return d->fi.isReadable() && !d->fi.isWritable();
372 }
373
374
375 bool FileName::isReadableDirectory() const
376 {
377         return d->fi.isDir() && d->fi.isReadable();
378 }
379
380
381 string FileName::onlyFileName() const
382 {
383         return fromqstr(d->fi.fileName());
384 }
385
386
387 string FileName::onlyFileNameWithoutExt() const
388 {
389        return fromqstr(d->fi.baseName());
390 }
391
392
393 string FileName::extension() const
394 {
395        return fromqstr(d->fi.suffix());
396 }
397
398
399 FileName FileName::onlyPath() const
400 {
401         FileName path;
402         path.d->fi.setFile(d->fi.path());
403         return path;
404 }
405
406
407 bool FileName::isReadableFile() const
408 {
409         return d->fi.isFile() && d->fi.isReadable();
410 }
411
412
413 bool FileName::isWritable() const
414 {
415         return d->fi.isWritable();
416 }
417
418
419 bool FileName::isDirWritable() const
420 {
421         LASSERT(d->fi.isDir(), return false);
422         QFileInfo tmp(d->fi.absoluteDir(), "lyxwritetest");
423         QTemporaryFile qt_tmp(tmp.absoluteFilePath());
424         if (qt_tmp.open()) {
425                 LYXERR(Debug::FILES, "Directory " << *this << " is writable");
426                 return true;
427         }
428         LYXERR(Debug::FILES, "Directory " << *this << " is not writable");
429         return false;
430 }
431
432
433 FileNameList FileName::dirList(string const & ext) const
434 {
435         FileNameList dirlist;
436         if (!isDirectory()) {
437                 LYXERR0("Directory '" << *this << "' does not exist!");
438                 return dirlist;
439         }
440
441         QDir dir = d->fi.absoluteDir();
442
443         if (!ext.empty()) {
444                 QString filter;
445                 switch (ext[0]) {
446                 case '.': filter = "*" + toqstr(ext); break;
447                 case '*': filter = toqstr(ext); break;
448                 default: filter = "*." + toqstr(ext);
449                 }
450                 dir.setNameFilters(QStringList(filter));
451                 LYXERR(Debug::FILES, "filtering on extension "
452                         << fromqstr(filter) << " is requested.");
453         }
454
455         QFileInfoList list = dir.entryInfoList();
456         for (int i = 0; i != list.size(); ++i) {
457                 FileName fi(fromqstr(list.at(i).absoluteFilePath()));
458                 dirlist.push_back(fi);
459                 LYXERR(Debug::FILES, "found file " << fi);
460         }
461
462         return dirlist;
463 }
464
465
466 static string createTempFile(QString const & mask)
467 {
468         QTemporaryFile qt_tmp(mask);
469         if (qt_tmp.open()) {
470                 string const temp_file = fromqstr(qt_tmp.fileName());
471                 LYXERR(Debug::FILES, "Temporary file `" << temp_file << "' created.");
472                 return temp_file;
473         }
474         LYXERR(Debug::FILES, "Unable to create temporary file with following template: "
475                 << qt_tmp.fileTemplate());
476         return string();
477 }
478
479
480 FileName FileName::tempName(FileName const & temp_dir, string const & mask)
481 {
482         QFileInfo tmp_fi(QDir(temp_dir.d->fi.absoluteFilePath()), toqstr(mask));
483         LYXERR(Debug::FILES, "Temporary file in " << tmp_fi.absoluteFilePath());
484         return FileName(createTempFile(tmp_fi.absoluteFilePath()));
485 }
486
487
488 FileName FileName::tempName(string const & mask)
489 {
490         return tempName(package().temp_dir(), mask);
491 }
492
493
494 FileName FileName::getcwd()
495 {
496         return FileName(".");
497 }
498
499
500 FileName FileName::tempPath()
501 {
502         return FileName(fromqstr(QDir::tempPath()));
503 }
504
505
506 time_t FileName::lastModified() const
507 {
508         // QFileInfo caches information about the file. So, in case this file has
509         // been touched between the object creation and now, we refresh the file
510         // information.
511         d->refresh();
512         return d->fi.lastModified().toTime_t();
513 }
514
515
516 bool FileName::chdir() const
517 {
518         return QDir::setCurrent(d->fi.absoluteFilePath());
519 }
520
521
522 unsigned long FileName::checksum() const
523 {
524         if (!exists()) {
525                 //LYXERR0("File \"" << absFilename() << "\" does not exist!");
526                 return 0;
527         }
528         // a directory may be passed here so we need to test it. (bug 3622)
529         if (isDirectory()) {
530                 LYXERR0('"' << absFilename() << "\" is a directory!");
531                 return 0;
532         }
533         if (0)//!lyxerr.debugging(Debug::FILES))
534                 return d->checksum();
535
536         QTime t;
537         t.start();
538         unsigned long r = d->checksum();
539         lyxerr << "Checksumming \"" << absFilename() << "\" "
540                 << r << " lasted " << t.elapsed() << " ms." << endl;
541         return r;
542 }
543
544
545 bool FileName::removeFile() const
546 {
547         bool const success = QFile::remove(d->fi.absoluteFilePath());
548         if (!success && exists())
549                 LYXERR0("Could not delete file " << *this);
550         return success;
551 }
552
553
554 static bool rmdir(QFileInfo const & fi)
555 {
556         QDir dir(fi.absoluteFilePath());
557         QFileInfoList list = dir.entryInfoList();
558         bool success = true;
559         for (int i = 0; i != list.size(); ++i) {
560                 if (list.at(i).fileName() == ".")
561                         continue;
562                 if (list.at(i).fileName() == "..")
563                         continue;
564                 bool removed;
565                 if (list.at(i).isDir()) {
566                         LYXERR(Debug::FILES, "Removing dir " 
567                                 << fromqstr(list.at(i).absoluteFilePath()));
568                         removed = rmdir(list.at(i));
569                 }
570                 else {
571                         LYXERR(Debug::FILES, "Removing file " 
572                                 << fromqstr(list.at(i).absoluteFilePath()));
573                         removed = dir.remove(list.at(i).fileName());
574                 }
575                 if (!removed) {
576                         success = false;
577                         LYXERR0("Could not delete "
578                                 << fromqstr(list.at(i).absoluteFilePath()));
579                 }
580         } 
581         QDir parent = fi.absolutePath();
582         success &= parent.rmdir(fi.fileName());
583         return success;
584 }
585
586
587 bool FileName::destroyDirectory() const
588 {
589         bool const success = rmdir(d->fi);
590         if (!success)
591                 LYXERR0("Could not delete " << *this);
592
593         return success;
594 }
595
596
597 static int mymkdir(char const * pathname, unsigned long int mode)
598 {
599         // FIXME: why don't we have mode_t in lyx::mkdir prototype ??
600 #if HAVE_MKDIR
601 # if MKDIR_TAKES_ONE_ARG
602         // MinGW32
603         return ::mkdir(pathname);
604         // FIXME: "Permissions of created directories are ignored on this system."
605 # else
606         // POSIX
607         return ::mkdir(pathname, mode_t(mode));
608 # endif
609 #elif defined(_WIN32)
610         // plain Windows 32
611         return CreateDirectory(pathname, 0) != 0 ? 0 : -1;
612         // FIXME: "Permissions of created directories are ignored on this system."
613 #elif HAVE__MKDIR
614         return ::_mkdir(pathname);
615         // FIXME: "Permissions of created directories are ignored on this system."
616 #else
617 #   error "Don't know how to create a directory on this system."
618 #endif
619
620 }
621
622
623 bool FileName::createDirectory(int permission) const
624 {
625         LASSERT(!empty(), /**/);
626         return mymkdir(toFilesystemEncoding().c_str(), permission) == 0;
627 }
628
629
630 bool FileName::createPath() const
631 {
632         LASSERT(!empty(), /**/);
633         if (isDirectory())
634                 return true;
635
636         QDir dir;
637         bool success = dir.mkpath(d->fi.absoluteFilePath());
638         if (!success)
639                 LYXERR0("Cannot create path '" << *this << "'!");
640         return success;
641 }
642
643
644 docstring const FileName::absoluteFilePath() const
645 {
646         return qstring_to_ucs4(d->fi.absoluteFilePath());
647 }
648
649
650 docstring FileName::displayName(int threshold) const
651 {
652         return makeDisplayPath(absFilename(), threshold);
653 }
654
655
656 docstring FileName::fileContents(string const & encoding) const
657 {
658         if (!isReadableFile()) {
659                 LYXERR0("File '" << *this << "' is not redable!");
660                 return docstring();
661         }
662
663         QFile file(d->fi.absoluteFilePath());
664         if (!file.open(QIODevice::ReadOnly)) {
665                 LYXERR0("File '" << *this
666                         << "' could not be opened in read only mode!");
667                 return docstring();
668         }
669         QByteArray contents = file.readAll();
670         file.close();
671
672         if (contents.isEmpty()) {
673                 LYXERR(Debug::FILES, "File '" << *this
674                         << "' is either empty or some error happened while reading it.");
675                 return docstring();
676         }
677
678         QString s;
679         if (encoding.empty() || encoding == "UTF-8")
680                 s = QString::fromUtf8(contents.data());
681         else if (encoding == "ascii")
682                 s = QString::fromAscii(contents.data());
683         else if (encoding == "local8bit")
684                 s = QString::fromLocal8Bit(contents.data());
685         else if (encoding == "latin1")
686                 s = QString::fromLatin1(contents.data());
687
688         return qstring_to_ucs4(s);
689 }
690
691
692 void FileName::changeExtension(string const & extension)
693 {
694         // FIXME: use Qt native methods...
695         string const oldname = absFilename();
696         string::size_type const last_slash = oldname.rfind('/');
697         string::size_type last_dot = oldname.rfind('.');
698         if (last_dot < last_slash && last_slash != string::npos)
699                 last_dot = string::npos;
700
701         string ext;
702         // Make sure the extension starts with a dot
703         if (!extension.empty() && extension[0] != '.')
704                 ext= '.' + extension;
705         else
706                 ext = extension;
707
708         set(oldname.substr(0, last_dot) + ext);
709 }
710
711
712 string FileName::guessFormatFromContents() const
713 {
714         // the different filetypes and what they contain in one of the first lines
715         // (dots are any characters).           (Herbert 20020131)
716         // AGR  Grace...
717         // BMP  BM...
718         // EPS  %!PS-Adobe-3.0 EPSF...
719         // FIG  #FIG...
720         // FITS ...BITPIX...
721         // GIF  GIF...
722         // JPG  JFIF
723         // PDF  %PDF-...
724         // PNG  .PNG...
725         // PBM  P1... or P4     (B/W)
726         // PGM  P2... or P5     (Grayscale)
727         // PPM  P3... or P6     (color)
728         // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
729         // SGI  \001\332...     (decimal 474)
730         // TGIF %TGIF...
731         // TIFF II... or MM...
732         // XBM  ..._bits[]...
733         // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
734         //      ...static char *...
735         // XWD  \000\000\000\151        (0x00006900) decimal 105
736         //
737         // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
738         // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
739         // Z    \037\235                UNIX compress
740
741         // paranoia check
742         if (empty() || !isReadableFile())
743                 return string();
744
745         ifstream ifs(toFilesystemEncoding().c_str());
746         if (!ifs)
747                 // Couldn't open file...
748                 return string();
749
750         // gnuzip
751         static string const gzipStamp = "\037\213";
752
753         // PKZIP
754         static string const zipStamp = "PK";
755
756         // compress
757         static string const compressStamp = "\037\235";
758
759         // Maximum strings to read
760         int const max_count = 50;
761         int count = 0;
762
763         string str;
764         string format;
765         bool firstLine = true;
766         while ((count++ < max_count) && format.empty()) {
767                 if (ifs.eof()) {
768                         LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
769                                 << "\tFile type not recognised before EOF!");
770                         break;
771                 }
772
773                 getline(ifs, str);
774                 string const stamp = str.substr(0, 2);
775                 if (firstLine && str.size() >= 2) {
776                         // at first we check for a zipped file, because this
777                         // information is saved in the first bytes of the file!
778                         // also some graphic formats which save the information
779                         // in the first line, too.
780                         if (prefixIs(str, gzipStamp)) {
781                                 format =  "gzip";
782
783                         } else if (stamp == zipStamp) {
784                                 format =  "zip";
785
786                         } else if (stamp == compressStamp) {
787                                 format =  "compress";
788
789                         // the graphics part
790                         } else if (stamp == "BM") {
791                                 format =  "bmp";
792
793                         } else if (stamp == "\001\332") {
794                                 format =  "sgi";
795
796                         // PBM family
797                         // Don't need to use str.at(0), str.at(1) because
798                         // we already know that str.size() >= 2
799                         } else if (str[0] == 'P') {
800                                 switch (str[1]) {
801                                 case '1':
802                                 case '4':
803                                         format =  "pbm";
804                                     break;
805                                 case '2':
806                                 case '5':
807                                         format =  "pgm";
808                                     break;
809                                 case '3':
810                                 case '6':
811                                         format =  "ppm";
812                                 }
813                                 break;
814
815                         } else if ((stamp == "II") || (stamp == "MM")) {
816                                 format =  "tiff";
817
818                         } else if (prefixIs(str,"%TGIF")) {
819                                 format =  "tgif";
820
821                         } else if (prefixIs(str,"#FIG")) {
822                                 format =  "fig";
823
824                         } else if (prefixIs(str,"GIF")) {
825                                 format =  "gif";
826
827                         } else if (str.size() > 3) {
828                                 int const c = ((str[0] << 24) & (str[1] << 16) &
829                                                (str[2] << 8)  & str[3]);
830                                 if (c == 105) {
831                                         format =  "xwd";
832                                 }
833                         }
834
835                         firstLine = false;
836                 }
837
838                 if (!format.empty())
839                     break;
840                 else if (contains(str,"EPSF"))
841                         // dummy, if we have wrong file description like
842                         // %!PS-Adobe-2.0EPSF"
843                         format = "eps";
844
845                 else if (contains(str, "Grace"))
846                         format = "agr";
847
848                 else if (contains(str, "JFIF"))
849                         format = "jpg";
850
851                 else if (contains(str, "%PDF"))
852                         format = "pdf";
853
854                 else if (contains(str, "PNG"))
855                         format = "png";
856
857                 else if (contains(str, "%!PS-Adobe")) {
858                         // eps or ps
859                         ifs >> str;
860                         if (contains(str,"EPSF"))
861                                 format = "eps";
862                         else
863                             format = "ps";
864                 }
865
866                 else if (contains(str, "_bits[]"))
867                         format = "xbm";
868
869                 else if (contains(str, "XPM") || contains(str, "static char *"))
870                         format = "xpm";
871
872                 else if (contains(str, "BITPIX"))
873                         format = "fits";
874         }
875
876         if (!format.empty()) {
877                 LYXERR(Debug::GRAPHICS, "Recognised Fileformat: " << format);
878                 return format;
879         }
880
881         LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
882                 << "\tCouldn't find a known format!");
883         return string();
884 }
885
886
887 bool FileName::isZippedFile() const
888 {
889         string const type = guessFormatFromContents();
890         return contains("gzip zip compress", type) && !type.empty();
891 }
892
893
894 docstring const FileName::relPath(string const & path) const
895 {
896         // FIXME UNICODE
897         return makeRelPath(absoluteFilePath(), from_utf8(path));
898 }
899
900
901 bool operator==(FileName const & lhs, FileName const & rhs)
902 {
903         // FIXME: We need to solve this warning from Qt documentation:
904         // * Long and short file names that refer to the same file on Windows are
905         //   treated as if they referred to different files.
906         // This is supposed to be fixed for Qt5.
907
908         if (lhs.empty())
909                 // QFileInfo::operator==() returns false if the two QFileInfo are empty.
910                 return rhs.empty();
911
912         if (rhs.empty())
913                 // Avoid unnecessary checks below.
914                 return false;
915
916         lhs.d->refresh();
917         rhs.d->refresh();
918         
919         if (!lhs.d->fi.isSymLink() && !rhs.d->fi.isSymLink())
920                 return lhs.d->fi == rhs.d->fi;
921
922         // FIXME: When/if QFileInfo support symlink comparison, remove this code.
923         QFileInfo fi1(lhs.d->fi);
924         if (fi1.isSymLink())
925                 fi1 = QFileInfo(fi1.symLinkTarget());
926         QFileInfo fi2(rhs.d->fi);
927         if (fi2.isSymLink())
928                 fi2 = QFileInfo(fi2.symLinkTarget());
929         return fi1 == fi2;
930 }
931
932
933 bool operator!=(FileName const & lhs, FileName const & rhs)
934 {
935         return !(operator==(lhs, rhs));
936 }
937
938
939 bool operator<(FileName const & lhs, FileName const & rhs)
940 {
941         return lhs.absFilename() < rhs.absFilename();
942 }
943
944
945 bool operator>(FileName const & lhs, FileName const & rhs)
946 {
947         return lhs.absFilename() > rhs.absFilename();
948 }
949
950
951 ostream & operator<<(ostream & os, FileName const & filename)
952 {
953         return os << filename.absFilename();
954 }
955
956
957 /////////////////////////////////////////////////////////////////////
958 //
959 // DocFileName
960 //
961 /////////////////////////////////////////////////////////////////////
962
963
964 DocFileName::DocFileName()
965         : save_abs_path_(true)
966 {}
967
968
969 DocFileName::DocFileName(string const & abs_filename, bool save_abs)
970         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
971 {}
972
973
974 DocFileName::DocFileName(FileName const & abs_filename, bool save_abs)
975         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
976 {}
977
978
979 void DocFileName::set(string const & name, string const & buffer_path)
980 {
981         FileName::set(name);
982         bool const nameIsAbsolute = isAbsolute();
983         save_abs_path_ = nameIsAbsolute;
984         if (!nameIsAbsolute)
985                 FileName::set(makeAbsPath(name, buffer_path).absFilename());
986         zipped_valid_ = false;
987 }
988
989
990 void DocFileName::erase()
991 {
992         FileName::erase();
993         zipped_valid_ = false;
994 }
995
996
997 string DocFileName::relFilename(string const & path) const
998 {
999         // FIXME UNICODE
1000         return to_utf8(relPath(path));
1001 }
1002
1003
1004 string DocFileName::outputFilename(string const & path) const
1005 {
1006         return save_abs_path_ ? absFilename() : relFilename(path);
1007 }
1008
1009
1010 string DocFileName::mangledFilename(string const & dir) const
1011 {
1012         // We need to make sure that every DocFileName instance for a given
1013         // filename returns the same mangled name.
1014         typedef map<string, string> MangledMap;
1015         static MangledMap mangledNames;
1016         MangledMap::const_iterator const it = mangledNames.find(absFilename());
1017         if (it != mangledNames.end())
1018                 return (*it).second;
1019
1020         string const name = absFilename();
1021         // Now the real work
1022         string mname = os::internal_path(name);
1023         // Remove the extension.
1024         mname = support::changeExtension(name, string());
1025         // The mangled name must be a valid LaTeX name.
1026         // The list of characters to keep is probably over-restrictive,
1027         // but it is not really a problem.
1028         // Apart from non-ASCII characters, at least the following characters
1029         // are forbidden: '/', '.', ' ', and ':'.
1030         // On windows it is not possible to create files with '<', '>' or '?'
1031         // in the name.
1032         static string const keep = "abcdefghijklmnopqrstuvwxyz"
1033                                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1034                                    "+,-0123456789;=";
1035         string::size_type pos = 0;
1036         while ((pos = mname.find_first_not_of(keep, pos)) != string::npos)
1037                 mname[pos++] = '_';
1038         // Add the extension back on
1039         mname = support::changeExtension(mname, getExtension(name));
1040
1041         // Prepend a counter to the filename. This is necessary to make
1042         // the mangled name unique.
1043         static int counter = 0;
1044         ostringstream s;
1045         s << counter++ << mname;
1046         mname = s.str();
1047
1048         // MiKTeX's YAP (version 2.4.1803) crashes if the file name
1049         // is longer than about 160 characters. MiKTeX's pdflatex
1050         // is even pickier. A maximum length of 100 has been proven to work.
1051         // If dir.size() > max length, all bets are off for YAP. We truncate
1052         // the filename nevertheless, keeping a minimum of 10 chars.
1053
1054         string::size_type max_length = max(100 - ((int)dir.size() + 1), 10);
1055
1056         // If the mangled file name is too long, hack it to fit.
1057         // We know we're guaranteed to have a unique file name because
1058         // of the counter.
1059         if (mname.size() > max_length) {
1060                 int const half = (int(max_length) / 2) - 2;
1061                 if (half > 0) {
1062                         mname = mname.substr(0, half) + "___" +
1063                                 mname.substr(mname.size() - half);
1064                 }
1065         }
1066
1067         mangledNames[absFilename()] = mname;
1068         return mname;
1069 }
1070
1071
1072 bool DocFileName::isZipped() const
1073 {
1074         if (!zipped_valid_) {
1075                 zipped_ = isZippedFile();
1076                 zipped_valid_ = true;
1077         }
1078         return zipped_;
1079 }
1080
1081
1082 string DocFileName::unzippedFilename() const
1083 {
1084         return unzippedFileName(absFilename());
1085 }
1086
1087
1088 bool operator==(DocFileName const & lhs, DocFileName const & rhs)
1089 {
1090         return static_cast<FileName const &>(lhs)
1091                 == static_cast<FileName const &>(rhs)
1092                 && lhs.saveAbsPath() == rhs.saveAbsPath();
1093 }
1094
1095
1096 bool operator!=(DocFileName const & lhs, DocFileName const & rhs)
1097 {
1098         return !(lhs == rhs);
1099 }
1100
1101 } // namespace support
1102 } // namespace lyx