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