]> git.lyx.org Git - lyx.git/blob - src/support/FileName.cpp
Compil fix.
[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/debug.h"
17 #include "support/filetools.h"
18 #include "support/lstrings.h"
19 #include "support/lyxlib.h"
20 #include "support/os.h"
21 #include "support/qstring_helpers.h"
22
23 #include <QDateTime>
24 #include <QDir>
25 #include <QFile>
26 #include <QFileInfo>
27 #include <QList>
28 #include <QTime>
29
30 #include <boost/assert.hpp>
31
32 #include <map>
33 #include <sstream>
34 #include <fstream>
35 #include <algorithm>
36
37 #ifdef HAVE_SYS_TYPES_H
38 # include <sys/types.h>
39 #endif
40 #ifdef HAVE_SYS_STAT_H
41 # include <sys/stat.h>
42 #endif
43 #include <cerrno>
44 #include <fcntl.h>
45
46 using namespace std;
47
48 namespace lyx {
49 namespace support {
50
51
52 /////////////////////////////////////////////////////////////////////
53 //
54 // FileName::Private
55 //
56 /////////////////////////////////////////////////////////////////////
57
58 struct FileName::Private
59 {
60         Private() {}
61
62         Private(string const & abs_filename) : fi(toqstr(abs_filename))
63         {
64                 fi.setCaching(fi.exists() ? true : false);
65         }
66         ///
67         QFileInfo fi;
68 };
69
70 /////////////////////////////////////////////////////////////////////
71 //
72 // FileName
73 //
74 /////////////////////////////////////////////////////////////////////
75
76
77 FileName::FileName() : d(new Private)
78 {
79 }
80
81
82 FileName::FileName(string const & abs_filename)
83         : d(abs_filename.empty() ? new Private : new Private(abs_filename))
84 {
85 }
86
87
88 FileName::~FileName()
89 {
90         delete d;
91 }
92
93
94 FileName::FileName(FileName const & rhs) : d(new Private)
95 {
96         d->fi = rhs.d->fi;
97 }
98
99
100 FileName & FileName::operator=(FileName const & rhs)
101 {
102         d->fi = rhs.d->fi;
103         return *this;
104 }
105
106
107 bool FileName::empty() const
108 {
109         return d->fi.absoluteFilePath().isEmpty();
110 }
111
112
113 string FileName::absFilename() const
114 {
115         return fromqstr(d->fi.absoluteFilePath());
116 }
117
118
119 void FileName::set(string const & name)
120 {
121         d->fi.setFile(toqstr(name));
122         BOOST_ASSERT(d->fi.isAbsolute());
123 }
124
125
126 void FileName::erase()
127 {
128         d->fi = QFileInfo();
129 }
130
131
132 bool FileName::copyTo(FileName const & name, bool overwrite) const
133 {
134         if (overwrite)
135                 QFile::remove(name.d->fi.absoluteFilePath());
136         bool success = QFile::copy(d->fi.absoluteFilePath(), name.d->fi.absoluteFilePath());
137         if (!success)
138                 lyxerr << "FileName::copyTo(): Could not copy file "
139                         << *this << " to " << name << endl;
140         return success;
141 }
142
143
144 bool FileName::renameTo(FileName const & name) const
145 {
146         bool success = QFile::rename(d->fi.absoluteFilePath(), name.d->fi.absoluteFilePath());
147         if (!success)
148                 LYXERR0("Could not rename file " << *this << " to " << name);
149         return success;
150 }
151
152
153 bool FileName::changePermission(unsigned long int mode) const
154 {
155         if (!isWritable()) {
156                 LYXERR0("File " << *this << " is not writable!");
157                 return false;
158         }
159
160 #if defined (HAVE_CHMOD) && defined (HAVE_MODE_T)
161         if (::chmod(toFilesystemEncoding().c_str(), mode_t(mode)) != 0) {
162                 LYXERR0("File " << *this << ": cannot change permission to "
163                         << mode << ".");
164                 return false;
165         }
166 #endif
167         return true;
168 }
169
170
171 string FileName::toFilesystemEncoding() const
172 {
173         QByteArray const encoded = QFile::encodeName(d->fi.absoluteFilePath());
174         return string(encoded.begin(), encoded.end());
175 }
176
177
178 FileName FileName::fromFilesystemEncoding(string const & name)
179 {
180         QByteArray const encoded(name.c_str(), name.length());
181         return FileName(fromqstr(QFile::decodeName(encoded)));
182 }
183
184
185 bool FileName::exists() const
186 {
187         return d->fi.exists();
188 }
189
190
191 bool FileName::isSymLink() const
192 {
193         return d->fi.isSymLink();
194 }
195
196
197 bool FileName::isFileEmpty() const
198 {
199         return d->fi.size() == 0;
200 }
201
202
203 bool FileName::isDirectory() const
204 {
205         return d->fi.isDir();
206 }
207
208
209 bool FileName::isReadOnly() const
210 {
211         return d->fi.isReadable() && !d->fi.isWritable();
212 }
213
214
215 bool FileName::isReadableDirectory() const
216 {
217         return d->fi.isDir() && d->fi.isReadable();
218 }
219
220
221 string FileName::onlyFileName() const
222 {
223         return support::onlyFilename(absFilename());
224 }
225
226
227 FileName FileName::onlyPath() const
228 {
229         return FileName(support::onlyPath(absFilename()));
230 }
231
232
233 bool FileName::isReadableFile() const
234 {
235         return d->fi.isFile() && d->fi.isReadable();
236 }
237
238
239 bool FileName::isWritable() const
240 {
241         return d->fi.isWritable();
242 }
243
244
245 bool FileName::isDirWritable() const
246 {
247         LYXERR(Debug::FILES, "isDirWriteable: " << *this);
248
249         FileName const tmpfl(tempName(*this, "lyxwritetest"));
250
251         if (tmpfl.empty())
252                 return false;
253
254         tmpfl.removeFile();
255         return true;
256 }
257
258
259 FileNameList FileName::dirList(string const & ext) const
260 {
261         FileNameList dirlist;
262         if (!isDirectory()) {
263                 LYXERR0("Directory '" << *this << "' does not exist!");
264                 return dirlist;
265         }
266
267         QDir dir = d->fi.absoluteDir();
268
269         if (!ext.empty()) {
270                 QString filter;
271                 switch (ext[0]) {
272                 case '.': filter = "*" + toqstr(ext); break;
273                 case '*': filter = toqstr(ext); break;
274                 default: filter = "*." + toqstr(ext);
275                 }
276                 dir.setNameFilters(QStringList(filter));
277                 LYXERR(Debug::FILES, "filtering on extension "
278                         << fromqstr(filter) << " is requested.");
279         }
280
281         QFileInfoList list = dir.entryInfoList();
282         for (int i = 0; i != list.size(); ++i) {
283                 FileName fi(fromqstr(list.at(i).absoluteFilePath()));
284                 dirlist.push_back(fi);
285                 LYXERR(Debug::FILES, "found file " << fi);
286         }
287
288         return dirlist;
289 }
290
291
292 FileName FileName::tempName(FileName const & dir, string const & mask)
293 {
294         return support::tempName(dir, mask);
295 }
296
297
298 time_t FileName::lastModified() const
299 {
300         return d->fi.lastModified().toTime_t();
301 }
302
303
304 bool FileName::chdir() const
305 {
306         return QDir::setCurrent(d->fi.absoluteFilePath());
307 }
308
309
310 extern unsigned long sum(char const * file);
311
312 unsigned long FileName::checksum() const
313 {
314         if (!exists()) {
315                 LYXERR0("File \"" << absFilename() << "\" does not exist!");
316                 return 0;
317         }
318         // a directory may be passed here so we need to test it. (bug 3622)
319         if (isDirectory()) {
320                 LYXERR0('"' << absFilename() << "\" is a directory!");
321                 return 0;
322         }
323         if (!lyxerr.debugging(Debug::FILES))
324                 return sum(absFilename().c_str());
325
326         QTime t;
327         t.start();
328         unsigned long r = sum(absFilename().c_str());
329         lyxerr << "Checksumming \"" << absFilename() << "\" lasted "
330                 << t.elapsed() << " ms." << endl;
331         return r;
332 }
333
334
335 bool FileName::removeFile() const
336 {
337         bool const success = QFile::remove(d->fi.absoluteFilePath());
338         if (!success && exists())
339                 LYXERR0("Could not delete file " << *this);
340         return success;
341 }
342
343
344 static bool rmdir(QFileInfo const & fi)
345 {
346         QDir dir(fi.absoluteFilePath());
347         QFileInfoList list = dir.entryInfoList();
348         bool success = true;
349         for (int i = 0; i != list.size(); ++i) {
350                 if (list.at(i).fileName() == ".")
351                         continue;
352                 if (list.at(i).fileName() == "..")
353                         continue;
354                 bool removed;
355                 if (list.at(i).isDir()) {
356                         LYXERR(Debug::FILES, "Removing dir " 
357                                 << fromqstr(list.at(i).absoluteFilePath()));
358                         removed = rmdir(list.at(i));
359                 }
360                 else {
361                         LYXERR(Debug::FILES, "Removing file " 
362                                 << fromqstr(list.at(i).absoluteFilePath()));
363                         removed = dir.remove(list.at(i).fileName());
364                 }
365                 if (!removed) {
366                         success = false;
367                         LYXERR0("Could not delete "
368                                 << fromqstr(list.at(i).absoluteFilePath()));
369                 }
370         } 
371         QDir parent = fi.absolutePath();
372         success &= parent.rmdir(fi.fileName());
373         return success;
374 }
375
376
377 bool FileName::destroyDirectory() const
378 {
379         bool const success = rmdir(d->fi);
380         if (!success)
381                 LYXERR0("Could not delete " << *this);
382
383         return success;
384 }
385
386
387 bool FileName::createDirectory(int permission) const
388 {
389         BOOST_ASSERT(!empty());
390         return mkdir(*this, permission) == 0;
391 }
392
393
394 docstring const FileName::absoluteFilePath() const
395 {
396         return qstring_to_ucs4(d->fi.absoluteFilePath());
397 }
398
399
400 docstring FileName::displayName(int threshold) const
401 {
402         return makeDisplayPath(absFilename(), threshold);
403 }
404
405
406 docstring FileName::fileContents(string const & encoding) const
407 {
408         if (!isReadableFile()) {
409                 LYXERR0("File '" << *this << "' is not redable!");
410                 return docstring();
411         }
412
413         QFile file(d->fi.absoluteFilePath());
414         if (!file.open(QIODevice::ReadOnly)) {
415                 LYXERR0("File '" << *this
416                         << "' could not be opened in read only mode!");
417                 return docstring();
418         }
419         QByteArray contents = file.readAll();
420         file.close();
421
422         if (contents.isEmpty()) {
423                 LYXERR(Debug::FILES, "File '" << *this
424                         << "' is either empty or some error happened while reading it.");
425                 return docstring();
426         }
427
428         QString s;
429         if (encoding.empty() || encoding == "UTF-8")
430                 s = QString::fromUtf8(contents.data());
431         else if (encoding == "ascii")
432                 s = QString::fromAscii(contents.data());
433         else if (encoding == "local8bit")
434                 s = QString::fromLocal8Bit(contents.data());
435         else if (encoding == "latin1")
436                 s = QString::fromLatin1(contents.data());
437
438         return qstring_to_ucs4(s);
439 }
440
441
442 void FileName::changeExtension(string const & extension)
443 {
444         // FIXME: use Qt native methods...
445         string const oldname = absFilename();
446         string::size_type const last_slash = oldname.rfind('/');
447         string::size_type last_dot = oldname.rfind('.');
448         if (last_dot < last_slash && last_slash != string::npos)
449                 last_dot = string::npos;
450
451         string ext;
452         // Make sure the extension starts with a dot
453         if (!extension.empty() && extension[0] != '.')
454                 ext= '.' + extension;
455         else
456                 ext = extension;
457
458         set(oldname.substr(0, last_dot) + ext);
459 }
460
461
462 string FileName::guessFormatFromContents() const
463 {
464         // the different filetypes and what they contain in one of the first lines
465         // (dots are any characters).           (Herbert 20020131)
466         // AGR  Grace...
467         // BMP  BM...
468         // EPS  %!PS-Adobe-3.0 EPSF...
469         // FIG  #FIG...
470         // FITS ...BITPIX...
471         // GIF  GIF...
472         // JPG  JFIF
473         // PDF  %PDF-...
474         // PNG  .PNG...
475         // PBM  P1... or P4     (B/W)
476         // PGM  P2... or P5     (Grayscale)
477         // PPM  P3... or P6     (color)
478         // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
479         // SGI  \001\332...     (decimal 474)
480         // TGIF %TGIF...
481         // TIFF II... or MM...
482         // XBM  ..._bits[]...
483         // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
484         //      ...static char *...
485         // XWD  \000\000\000\151        (0x00006900) decimal 105
486         //
487         // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
488         // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
489         // Z    \037\235                UNIX compress
490
491         // paranoia check
492         if (empty() || !isReadableFile())
493                 return string();
494
495         ifstream ifs(toFilesystemEncoding().c_str());
496         if (!ifs)
497                 // Couldn't open file...
498                 return string();
499
500         // gnuzip
501         static string const gzipStamp = "\037\213";
502
503         // PKZIP
504         static string const zipStamp = "PK";
505
506         // compress
507         static string const compressStamp = "\037\235";
508
509         // Maximum strings to read
510         int const max_count = 50;
511         int count = 0;
512
513         string str;
514         string format;
515         bool firstLine = true;
516         while ((count++ < max_count) && format.empty()) {
517                 if (ifs.eof()) {
518                         LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
519                                 << "\tFile type not recognised before EOF!");
520                         break;
521                 }
522
523                 getline(ifs, str);
524                 string const stamp = str.substr(0, 2);
525                 if (firstLine && str.size() >= 2) {
526                         // at first we check for a zipped file, because this
527                         // information is saved in the first bytes of the file!
528                         // also some graphic formats which save the information
529                         // in the first line, too.
530                         if (prefixIs(str, gzipStamp)) {
531                                 format =  "gzip";
532
533                         } else if (stamp == zipStamp) {
534                                 format =  "zip";
535
536                         } else if (stamp == compressStamp) {
537                                 format =  "compress";
538
539                         // the graphics part
540                         } else if (stamp == "BM") {
541                                 format =  "bmp";
542
543                         } else if (stamp == "\001\332") {
544                                 format =  "sgi";
545
546                         // PBM family
547                         // Don't need to use str.at(0), str.at(1) because
548                         // we already know that str.size() >= 2
549                         } else if (str[0] == 'P') {
550                                 switch (str[1]) {
551                                 case '1':
552                                 case '4':
553                                         format =  "pbm";
554                                     break;
555                                 case '2':
556                                 case '5':
557                                         format =  "pgm";
558                                     break;
559                                 case '3':
560                                 case '6':
561                                         format =  "ppm";
562                                 }
563                                 break;
564
565                         } else if ((stamp == "II") || (stamp == "MM")) {
566                                 format =  "tiff";
567
568                         } else if (prefixIs(str,"%TGIF")) {
569                                 format =  "tgif";
570
571                         } else if (prefixIs(str,"#FIG")) {
572                                 format =  "fig";
573
574                         } else if (prefixIs(str,"GIF")) {
575                                 format =  "gif";
576
577                         } else if (str.size() > 3) {
578                                 int const c = ((str[0] << 24) & (str[1] << 16) &
579                                                (str[2] << 8)  & str[3]);
580                                 if (c == 105) {
581                                         format =  "xwd";
582                                 }
583                         }
584
585                         firstLine = false;
586                 }
587
588                 if (!format.empty())
589                     break;
590                 else if (contains(str,"EPSF"))
591                         // dummy, if we have wrong file description like
592                         // %!PS-Adobe-2.0EPSF"
593                         format = "eps";
594
595                 else if (contains(str, "Grace"))
596                         format = "agr";
597
598                 else if (contains(str, "JFIF"))
599                         format = "jpg";
600
601                 else if (contains(str, "%PDF"))
602                         format = "pdf";
603
604                 else if (contains(str, "PNG"))
605                         format = "png";
606
607                 else if (contains(str, "%!PS-Adobe")) {
608                         // eps or ps
609                         ifs >> str;
610                         if (contains(str,"EPSF"))
611                                 format = "eps";
612                         else
613                             format = "ps";
614                 }
615
616                 else if (contains(str, "_bits[]"))
617                         format = "xbm";
618
619                 else if (contains(str, "XPM") || contains(str, "static char *"))
620                         format = "xpm";
621
622                 else if (contains(str, "BITPIX"))
623                         format = "fits";
624         }
625
626         if (!format.empty()) {
627                 LYXERR(Debug::GRAPHICS, "Recognised Fileformat: " << format);
628                 return format;
629         }
630
631         LYXERR(Debug::GRAPHICS, "filetools(getFormatFromContents)\n"
632                 << "\tCouldn't find a known format!");
633         return string();
634 }
635
636
637 bool FileName::isZippedFile() const
638 {
639         string const type = guessFormatFromContents();
640         return contains("gzip zip compress", type) && !type.empty();
641 }
642
643
644 docstring const FileName::relPath(string const & path) const
645 {
646         // FIXME UNICODE
647         return makeRelPath(absoluteFilePath(), from_utf8(path));
648 }
649
650
651 bool operator==(FileName const & lhs, FileName const & rhs)
652 {
653         return lhs.absFilename() == rhs.absFilename();
654 }
655
656
657 bool operator!=(FileName const & lhs, FileName const & rhs)
658 {
659         return lhs.absFilename() != rhs.absFilename();
660 }
661
662
663 bool operator<(FileName const & lhs, FileName const & rhs)
664 {
665         return lhs.absFilename() < rhs.absFilename();
666 }
667
668
669 bool operator>(FileName const & lhs, FileName const & rhs)
670 {
671         return lhs.absFilename() > rhs.absFilename();
672 }
673
674
675 ostream & operator<<(ostream & os, FileName const & filename)
676 {
677         return os << filename.absFilename();
678 }
679
680
681 /////////////////////////////////////////////////////////////////////
682 //
683 // DocFileName
684 //
685 /////////////////////////////////////////////////////////////////////
686
687
688 DocFileName::DocFileName()
689         : save_abs_path_(true)
690 {}
691
692
693 DocFileName::DocFileName(string const & abs_filename, bool save_abs)
694         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
695 {}
696
697
698 DocFileName::DocFileName(FileName const & abs_filename, bool save_abs)
699         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
700 {}
701
702
703 void DocFileName::set(string const & name, string const & buffer_path)
704 {
705         save_abs_path_ = absolutePath(name);
706         FileName::set(save_abs_path_ ? name : makeAbsPath(name, buffer_path).absFilename());
707         zipped_valid_ = false;
708 }
709
710
711 void DocFileName::erase()
712 {
713         FileName::erase();
714         zipped_valid_ = false;
715 }
716
717
718 string DocFileName::relFilename(string const & path) const
719 {
720         // FIXME UNICODE
721         return to_utf8(relPath(path));
722 }
723
724
725 string DocFileName::outputFilename(string const & path) const
726 {
727         return save_abs_path_ ? absFilename() : relFilename(path);
728 }
729
730
731 string DocFileName::mangledFilename(string const & dir) const
732 {
733         // We need to make sure that every DocFileName instance for a given
734         // filename returns the same mangled name.
735         typedef map<string, string> MangledMap;
736         static MangledMap mangledNames;
737         MangledMap::const_iterator const it = mangledNames.find(absFilename());
738         if (it != mangledNames.end())
739                 return (*it).second;
740
741         string const name = absFilename();
742         // Now the real work
743         string mname = os::internal_path(name);
744         // Remove the extension.
745         mname = support::changeExtension(name, string());
746         // The mangled name must be a valid LaTeX name.
747         // The list of characters to keep is probably over-restrictive,
748         // but it is not really a problem.
749         // Apart from non-ASCII characters, at least the following characters
750         // are forbidden: '/', '.', ' ', and ':'.
751         // On windows it is not possible to create files with '<', '>' or '?'
752         // in the name.
753         static string const keep = "abcdefghijklmnopqrstuvwxyz"
754                                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
755                                    "+,-0123456789;=";
756         string::size_type pos = 0;
757         while ((pos = mname.find_first_not_of(keep, pos)) != string::npos)
758                 mname[pos++] = '_';
759         // Add the extension back on
760         mname = support::changeExtension(mname, getExtension(name));
761
762         // Prepend a counter to the filename. This is necessary to make
763         // the mangled name unique.
764         static int counter = 0;
765         ostringstream s;
766         s << counter++ << mname;
767         mname = s.str();
768
769         // MiKTeX's YAP (version 2.4.1803) crashes if the file name
770         // is longer than about 160 characters. MiKTeX's pdflatex
771         // is even pickier. A maximum length of 100 has been proven to work.
772         // If dir.size() > max length, all bets are off for YAP. We truncate
773         // the filename nevertheless, keeping a minimum of 10 chars.
774
775         string::size_type max_length = max(100 - ((int)dir.size() + 1), 10);
776
777         // If the mangled file name is too long, hack it to fit.
778         // We know we're guaranteed to have a unique file name because
779         // of the counter.
780         if (mname.size() > max_length) {
781                 int const half = (int(max_length) / 2) - 2;
782                 if (half > 0) {
783                         mname = mname.substr(0, half) + "___" +
784                                 mname.substr(mname.size() - half);
785                 }
786         }
787
788         mangledNames[absFilename()] = mname;
789         return mname;
790 }
791
792
793 bool DocFileName::isZipped() const
794 {
795         if (!zipped_valid_) {
796                 zipped_ = isZippedFile();
797                 zipped_valid_ = true;
798         }
799         return zipped_;
800 }
801
802
803 string DocFileName::unzippedFilename() const
804 {
805         return unzippedFileName(absFilename());
806 }
807
808
809 bool operator==(DocFileName const & lhs, DocFileName const & rhs)
810 {
811         return lhs.absFilename() == rhs.absFilename()
812                 && lhs.saveAbsPath() == rhs.saveAbsPath();
813 }
814
815
816 bool operator!=(DocFileName const & lhs, DocFileName const & rhs)
817 {
818         return !(lhs == rhs);
819 }
820
821 } // namespace support
822 } // namespace lyx