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