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