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