]> git.lyx.org Git - lyx.git/blob - src/support/FileName.cpp
cosmetics
[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 <QFile>
23 #include <QFileInfo>
24
25 #include <boost/filesystem/exception.hpp>
26 #include <boost/filesystem/operations.hpp>
27
28 #include <map>
29 #include <sstream>
30 #include <fstream>
31 #include <algorithm>
32
33
34 using std::map;
35 using std::string;
36 using std::ifstream;
37 using std::ostringstream;
38 using std::endl;
39
40 namespace fs = boost::filesystem;
41
42 namespace lyx {
43 namespace support {
44
45
46 /////////////////////////////////////////////////////////////////////
47 //
48 // FileName
49 //
50 /////////////////////////////////////////////////////////////////////
51
52
53 FileName::FileName(string const & abs_filename)
54         : name_(abs_filename)
55 {
56         BOOST_ASSERT(empty() || absolutePath(name_));
57 #if defined(_WIN32)
58         BOOST_ASSERT(!contains(name_, '\\'));
59 #endif
60 }
61
62
63 void FileName::set(string const & name)
64 {
65         name_ = name;
66         BOOST_ASSERT(absolutePath(name_));
67 #if defined(_WIN32)
68         BOOST_ASSERT(!contains(name_, '\\'));
69 #endif
70 }
71
72
73 void FileName::erase()
74 {
75         name_.erase();
76 }
77
78
79 string FileName::toFilesystemEncoding() const
80 {
81         QByteArray const encoded = QFile::encodeName(toqstr(name_));
82         return string(encoded.begin(), encoded.end());
83 }
84
85
86 FileName FileName::fromFilesystemEncoding(string const & name)
87 {
88         QByteArray const encoded(name.c_str(), name.length());
89         return FileName(fromqstr(QFile::decodeName(encoded)));
90 }
91
92
93 bool FileName::exists() const
94 {
95         return QFileInfo(toqstr(name_)).exists();
96 }
97
98
99 bool FileName::isDirectory() const
100 {
101         return QFileInfo(toqstr(name_)).isDir();
102 }
103
104
105 bool FileName::isReadOnly() const
106 {
107         QFileInfo const fi(toqstr(name_));
108         return fi.isReadable() && !fi.isWritable();
109 }
110
111
112 bool FileName::isReadable() const
113 {
114         QFileInfo const fi(toqstr(name_));
115         return fi.isReadable();
116 }
117
118
119 bool FileName::isFileReadable() const
120 {
121         QFileInfo const fi(toqstr(name_));
122         return fi.isFile() && fi.isReadable();
123 }
124
125
126 bool FileName::isWritable() const
127 {
128         QFileInfo const fi(toqstr(name_));
129         return fi.isWritable();
130 }
131
132
133 bool FileName::isDirWritable() const
134 {
135         LYXERR(Debug::FILES) << "isDirWriteable: " << *this << std::endl;
136
137         FileName const tmpfl(tempName(*this, "lyxwritetest"));
138
139         if (tmpfl.empty())
140                 return false;
141
142         unlink(tmpfl);
143         return true;
144 }
145
146
147 FileName FileName::tempName(FileName const & dir, std::string const & mask)
148 {
149         return support::tempName(dir, mask);
150 }
151
152
153 std::time_t FileName::lastModified() const
154 {
155         return fs::last_write_time(toFilesystemEncoding());
156 }
157
158
159 bool FileName::destroyDirectory() const
160 {
161         try {
162                 return fs::remove_all(toFilesystemEncoding()) > 0;
163         } catch (fs::filesystem_error const & fe){
164                 lyxerr << "Could not delete " << *this << ". (" << fe.what() << ")"
165                         << std::endl;
166                 return false;
167         }
168 }
169
170
171 bool FileName::createDirectory(int permission) const
172 {
173         BOOST_ASSERT(!empty());
174         return mkdir(*this, permission) == 0;
175 }
176
177
178 string FileName::fileContents() const
179 {
180         if (exists()) {
181                 string const encodedname = toFilesystemEncoding();
182                 ifstream ifs(encodedname.c_str());
183                 ostringstream ofs;
184                 if (ifs && ofs) {
185                         ofs << ifs.rdbuf();
186                         ifs.close();
187                         return ofs.str();
188                 }
189         }
190         lyxerr << "LyX was not able to read file '" << *this << '\'' << std::endl;
191         return string();
192 }
193
194
195 string FileName::guessFormatFromContents() const
196 {
197         // the different filetypes and what they contain in one of the first lines
198         // (dots are any characters).           (Herbert 20020131)
199         // AGR  Grace...
200         // BMP  BM...
201         // EPS  %!PS-Adobe-3.0 EPSF...
202         // FIG  #FIG...
203         // FITS ...BITPIX...
204         // GIF  GIF...
205         // JPG  JFIF
206         // PDF  %PDF-...
207         // PNG  .PNG...
208         // PBM  P1... or P4     (B/W)
209         // PGM  P2... or P5     (Grayscale)
210         // PPM  P3... or P6     (color)
211         // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
212         // SGI  \001\332...     (decimal 474)
213         // TGIF %TGIF...
214         // TIFF II... or MM...
215         // XBM  ..._bits[]...
216         // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
217         //      ...static char *...
218         // XWD  \000\000\000\151        (0x00006900) decimal 105
219         //
220         // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
221         // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
222         // Z    \037\235                UNIX compress
223         // paranoia check
224
225         if (empty() || !isReadable())
226                 return string();
227
228         ifstream ifs(toFilesystemEncoding().c_str());
229         if (!ifs)
230                 // Couldn't open file...
231                 return string();
232
233         // gnuzip
234         static string const gzipStamp = "\037\213";
235
236         // PKZIP
237         static string const zipStamp = "PK";
238
239         // compress
240         static string const compressStamp = "\037\235";
241
242         // Maximum strings to read
243         int const max_count = 50;
244         int count = 0;
245
246         string str;
247         string format;
248         bool firstLine = true;
249         while ((count++ < max_count) && format.empty()) {
250                 if (ifs.eof()) {
251                         LYXERR(Debug::GRAPHICS)
252                                 << "filetools(getFormatFromContents)\n"
253                                 << "\tFile type not recognised before EOF!"
254                                 << endl;
255                         break;
256                 }
257
258                 getline(ifs, str);
259                 string const stamp = str.substr(0, 2);
260                 if (firstLine && str.size() >= 2) {
261                         // at first we check for a zipped file, because this
262                         // information is saved in the first bytes of the file!
263                         // also some graphic formats which save the information
264                         // in the first line, too.
265                         if (prefixIs(str, gzipStamp)) {
266                                 format =  "gzip";
267
268                         } else if (stamp == zipStamp) {
269                                 format =  "zip";
270
271                         } else if (stamp == compressStamp) {
272                                 format =  "compress";
273
274                         // the graphics part
275                         } else if (stamp == "BM") {
276                                 format =  "bmp";
277
278                         } else if (stamp == "\001\332") {
279                                 format =  "sgi";
280
281                         // PBM family
282                         // Don't need to use str.at(0), str.at(1) because
283                         // we already know that str.size() >= 2
284                         } else if (str[0] == 'P') {
285                                 switch (str[1]) {
286                                 case '1':
287                                 case '4':
288                                         format =  "pbm";
289                                     break;
290                                 case '2':
291                                 case '5':
292                                         format =  "pgm";
293                                     break;
294                                 case '3':
295                                 case '6':
296                                         format =  "ppm";
297                                 }
298                                 break;
299
300                         } else if ((stamp == "II") || (stamp == "MM")) {
301                                 format =  "tiff";
302
303                         } else if (prefixIs(str,"%TGIF")) {
304                                 format =  "tgif";
305
306                         } else if (prefixIs(str,"#FIG")) {
307                                 format =  "fig";
308
309                         } else if (prefixIs(str,"GIF")) {
310                                 format =  "gif";
311
312                         } else if (str.size() > 3) {
313                                 int const c = ((str[0] << 24) & (str[1] << 16) &
314                                                (str[2] << 8)  & str[3]);
315                                 if (c == 105) {
316                                         format =  "xwd";
317                                 }
318                         }
319
320                         firstLine = false;
321                 }
322
323                 if (!format.empty())
324                     break;
325                 else if (contains(str,"EPSF"))
326                         // dummy, if we have wrong file description like
327                         // %!PS-Adobe-2.0EPSF"
328                         format = "eps";
329
330                 else if (contains(str, "Grace"))
331                         format = "agr";
332
333                 else if (contains(str, "JFIF"))
334                         format = "jpg";
335
336                 else if (contains(str, "%PDF"))
337                         format = "pdf";
338
339                 else if (contains(str, "PNG"))
340                         format = "png";
341
342                 else if (contains(str, "%!PS-Adobe")) {
343                         // eps or ps
344                         ifs >> str;
345                         if (contains(str,"EPSF"))
346                                 format = "eps";
347                         else
348                             format = "ps";
349                 }
350
351                 else if (contains(str, "_bits[]"))
352                         format = "xbm";
353
354                 else if (contains(str, "XPM") || contains(str, "static char *"))
355                         format = "xpm";
356
357                 else if (contains(str, "BITPIX"))
358                         format = "fits";
359         }
360
361         if (!format.empty()) {
362                 LYXERR(Debug::GRAPHICS)
363                         << "Recognised Fileformat: " << format << endl;
364                 return format;
365         }
366
367         LYXERR(Debug::GRAPHICS)
368                 << "filetools(getFormatFromContents)\n"
369                 << "\tCouldn't find a known format!\n";
370         return string();
371 }
372
373
374 bool FileName::isZippedFile() const
375 {
376         string const type = guessFormatFromContents();
377         return contains("gzip zip compress", type) && !type.empty();
378 }
379
380
381 bool operator==(FileName const & lhs, FileName const & rhs)
382 {
383         return lhs.absFilename() == rhs.absFilename();
384 }
385
386
387 bool operator!=(FileName const & lhs, FileName const & rhs)
388 {
389         return lhs.absFilename() != rhs.absFilename();
390 }
391
392
393 bool operator<(FileName const & lhs, FileName const & rhs)
394 {
395         return lhs.absFilename() < rhs.absFilename();
396 }
397
398
399 bool operator>(FileName const & lhs, FileName const & rhs)
400 {
401         return lhs.absFilename() > rhs.absFilename();
402 }
403
404
405 std::ostream & operator<<(std::ostream & os, FileName const & filename)
406 {
407         return os << filename.absFilename();
408 }
409
410
411 /////////////////////////////////////////////////////////////////////
412 //
413 // DocFileName
414 //
415 /////////////////////////////////////////////////////////////////////
416
417
418 DocFileName::DocFileName()
419         : save_abs_path_(true)
420 {}
421
422
423 DocFileName::DocFileName(string const & abs_filename, bool save_abs)
424         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
425 {}
426
427
428 DocFileName::DocFileName(FileName const & abs_filename, bool save_abs)
429         : FileName(abs_filename), save_abs_path_(save_abs), zipped_valid_(false)
430 {}
431
432
433 void DocFileName::set(string const & name, string const & buffer_path)
434 {
435         save_abs_path_ = absolutePath(name);
436         name_ = save_abs_path_ ? name : makeAbsPath(name, buffer_path).absFilename();
437         zipped_valid_ = false;
438 }
439
440
441 void DocFileName::erase()
442 {
443         name_.erase();
444         zipped_valid_ = false;
445 }
446
447
448 string const DocFileName::relFilename(string const & path) const
449 {
450         // FIXME UNICODE
451         return to_utf8(makeRelPath(from_utf8(name_), from_utf8(path)));
452 }
453
454
455 string const DocFileName::outputFilename(string const & path) const
456 {
457         // FIXME UNICODE
458         return save_abs_path_ ? name_ : to_utf8(makeRelPath(from_utf8(name_), from_utf8(path)));
459 }
460
461
462 string const DocFileName::mangledFilename(std::string const & dir) const
463 {
464         // We need to make sure that every DocFileName instance for a given
465         // filename returns the same mangled name.
466         typedef map<string, string> MangledMap;
467         static MangledMap mangledNames;
468         MangledMap::const_iterator const it = mangledNames.find(name_);
469         if (it != mangledNames.end())
470                 return (*it).second;
471
472         // Now the real work
473         string mname = os::internal_path(name_);
474         // Remove the extension.
475         mname = changeExtension(name_, string());
476         // The mangled name must be a valid LaTeX name.
477         // The list of characters to keep is probably over-restrictive,
478         // but it is not really a problem.
479         // Apart from non-ASCII characters, at least the following characters
480         // are forbidden: '/', '.', ' ', and ':'.
481         // On windows it is not possible to create files with '<', '>' or '?'
482         // in the name.
483         static string const keep = "abcdefghijklmnopqrstuvwxyz"
484                                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
485                                    "+,-0123456789;=";
486         string::size_type pos = 0;
487         while ((pos = mname.find_first_not_of(keep, pos)) != string::npos)
488                 mname[pos++] = '_';
489         // Add the extension back on
490         mname = changeExtension(mname, getExtension(name_));
491
492         // Prepend a counter to the filename. This is necessary to make
493         // the mangled name unique.
494         static int counter = 0;
495         std::ostringstream s;
496         s << counter++ << mname;
497         mname = s.str();
498
499         // MiKTeX's YAP (version 2.4.1803) crashes if the file name
500         // is longer than about 160 characters. MiKTeX's pdflatex
501         // is even pickier. A maximum length of 100 has been proven to work.
502         // If dir.size() > max length, all bets are off for YAP. We truncate
503         // the filename nevertheless, keeping a minimum of 10 chars.
504
505         string::size_type max_length = std::max(100 - ((int)dir.size() + 1), 10);
506
507         // If the mangled file name is too long, hack it to fit.
508         // We know we're guaranteed to have a unique file name because
509         // of the counter.
510         if (mname.size() > max_length) {
511                 int const half = (int(max_length) / 2) - 2;
512                 if (half > 0) {
513                         mname = mname.substr(0, half) + "___" +
514                                 mname.substr(mname.size() - half);
515                 }
516         }
517
518         mangledNames[name_] = mname;
519         return mname;
520 }
521
522
523 bool DocFileName::isZipped() const
524 {
525         if (!zipped_valid_) {
526                 zipped_ = isZippedFile();
527                 zipped_valid_ = true;
528         }
529         return zipped_;
530 }
531
532
533 string const DocFileName::unzippedFilename() const
534 {
535         return unzippedFileName(name_);
536 }
537
538
539 bool operator==(DocFileName const & lhs, DocFileName const & rhs)
540 {
541         return lhs.absFilename() == rhs.absFilename()
542                 && lhs.saveAbsPath() == rhs.saveAbsPath();
543 }
544
545
546 bool operator!=(DocFileName const & lhs, DocFileName const & rhs)
547 {
548         return !(lhs == rhs);
549 }
550
551 } // namespace support
552 } // namespace lyx