]> git.sven.stormbind.net Git - sven/vym.git/blob - src/file.cpp
Replace Pierre as the maintainer
[sven/vym.git] / src / file.cpp
1 #include <QDebug>
2 #include <QDir>
3 #include <QLabel>
4 #include <QMessageBox>
5 #include <QOperatingSystemVersion>
6 #include <QPixmap>
7 #include <QTextStream>
8 #include <cstdlib>
9 #include <iostream>
10
11 #include "file.h"
12 #include "vymprocess.h"
13
14 #if defined(Q_OS_WINDOWS)
15 #include "mkdtemp.h"
16 #include <windows.h>
17 #endif
18
19 #if defined(Q_OS_MACX)
20 #include "unistd.h"
21 #endif
22
23 using namespace File;
24
25 extern QString zipToolPath;
26 extern QString unzipToolPath;
27 extern bool zipToolAvailable;
28 extern bool unzipToolAvailable;
29
30 QString convertToRel(const QString &src, const QString &dst)
31 {
32     // Creates a relative path pointing from src to dst
33
34     QString s = src;
35     QString d = dst;
36     int i;
37
38     if (s == d) {
39         // Special case, we just need the name of the file,
40         // not the complete path
41         i = d.lastIndexOf("/");
42         d = d.right(d.length() - i - 1);
43     }
44     else {
45         // remove identical left parts
46         while (s.section("/", 0, 0) == d.section("/", 0, 0)) {
47             i = s.indexOf("/");
48             s = s.right(s.length() - i - 1);
49             d = d.right(d.length() - i - 1);
50         }
51
52         // Now take care of paths where we have to go back first
53         int srcsep = s.count("/");
54         while (srcsep > 0) {
55             d = "../" + d;
56             srcsep--;
57         }
58     }
59     return d;
60 }
61
62 QString convertToAbs(const QString &src, const QString &dst)
63 {
64     // Creates a relative path pointing from src to dst
65     QDir dd(src);
66     return dd.absoluteFilePath(dst);
67 }
68
69 QString basename(const QString &path) { return path.section('/', -1); }
70
71 QString dirname(const QString &path) { return path.section('/', 0, -2); }
72
73 extern QString vymName;
74 bool confirmDirectoryOverwrite(const QDir &dir)
75 {
76     if (!dir.exists()) {
77         qWarning() << "Directory does not exist: " << dir.path();
78         return false;
79     }
80
81     QStringList eList = dir.entryList();
82     while (!eList.isEmpty() && (eList.first() == "." || eList.first() == ".."))
83         eList.pop_front(); // remove "." and ".."
84
85     if (!eList.isEmpty()) {
86         QMessageBox mb(vymName,
87                        QObject::tr("The directory %1 is not empty.\nDo you "
88                                    "risk to overwrite its contents?",
89                                    "write directory")
90                            .arg(dir.path()),
91                        QMessageBox::Warning, QMessageBox::Yes,
92                        QMessageBox::Cancel | QMessageBox::Default,
93                        QMessageBox::NoButton);
94
95         mb.setButtonText(QMessageBox::Yes, QObject::tr("Overwrite"));
96         mb.setButtonText(QMessageBox::No, QObject::tr("Cancel"));
97         switch (mb.exec()) {
98         case QMessageBox::Yes:
99             // save
100             return true;
101         case QMessageBox::Cancel:
102             // do nothing
103             return false;
104         }
105     }
106     return true;
107 }
108
109 QString makeTmpDir(bool &ok, const QString &dirPath,
110                    const QString &prefix)
111 {
112     QString path = makeUniqueDir(ok, dirPath + "/" + prefix + "-XXXXXX");
113     return path;
114 }
115
116 QString makeTmpDir(bool &ok, const QString &prefix)
117 {
118     return makeTmpDir(ok, QDir::tempPath(), prefix);
119 }
120
121 bool isInTmpDir(QString fn)
122 {
123     QString temp = QDir::tempPath();
124     int l = temp.length();
125     return fn.left(l) == temp;
126 }
127
128 QString makeUniqueDir(bool &ok, QString s) // FIXME-3 use QTemporaryDir
129 {
130     ok = true;
131
132     QString r;
133
134 #if defined(Q_OS_WINDOWS)
135     r = mkdtemp(s);
136 #else
137     // On Linux and friends use cstdlib
138
139     // Convert QString to string
140     ok = true;
141     char *p;
142     int bytes = s.length();
143     p = (char *)malloc(bytes + 1);
144     int i;
145     for (i = 0; i < bytes; i++)
146         p[i] = s.at(i).unicode();
147     p[bytes] = 0;
148
149     r = mkdtemp(p);
150     free(p);
151 #endif
152
153     if (r.isEmpty())
154         ok = false;
155     return r;
156 }
157
158 bool removeDir(QDir d)
159 {
160     // This check should_ not be necessary, but proved to be useful ;-)
161     if (!isInTmpDir(d.path())) {
162         qWarning() << "file.cpp::removeDir should remove " + d.path() +
163                           " - aborted.";
164         return false;
165     }
166
167     return d.removeRecursively();
168 }
169
170 bool copyDir(QDir src, QDir dst, const bool &override)
171 {
172     QStringList dirs =
173         src.entryList(QDir::AllDirs | QDir::Hidden | QDir::NoDotAndDotDot);
174     QStringList files = src.entryList(QDir::Files);
175
176     // Check if dst is a subdir of src, which would cause endless recursion...
177     if (dst.absolutePath().contains(src.absolutePath()))
178         return false;
179
180     // Traverse directories
181     QList<QString>::iterator d, f;
182     for (d = dirs.begin(); d != dirs.end(); ++d) {
183         if (!QFileInfo(src.path() + "/" + (*d)).isDir())
184             continue;
185
186         QDir cdir(dst.path() + "/" + (*d));
187         cdir.mkpath(cdir.path());
188
189         if (!copyDir(QDir(src.path() + "/" + (*d)),
190                      QDir(dst.path() + "/" + (*d)), override))
191             return false;
192     }
193
194     // Traverse files
195     for (f = files.begin(); f != files.end(); ++f) {
196         QFile cfile(src.path() + "/" + (*f));
197         QFile destFile(dst.path() + "/" +
198                        src.relativeFilePath(cfile.fileName()));
199         if (destFile.exists() && override)
200             destFile.remove();
201
202         if (!cfile.copy(dst.path() + "/" +
203                         src.relativeFilePath(cfile.fileName())))
204             return false;
205     }
206     return true;
207 }
208
209 bool subDirsExist()
210 {
211     QStringList dirList;
212     dirList << "images";
213     dirList << "flags";
214     dirList << "flags/user";
215     dirList << "flags/standard";
216     foreach (QString d, dirList)
217         if (QDir(d).exists() ) return true;
218
219     return false;
220 }
221
222 void makeSubDirs(const QString &s)
223 {
224     QDir d(s);
225     d.mkdir(s);
226     d.mkdir("images");
227     d.mkdir("flags");
228     d.mkdir("flags/user");
229     d.mkdir("flags/standard");
230 }
231
232 bool checkZipTool()
233 {
234     zipToolAvailable = false;
235 #if defined(Q_OS_WINDOWS)
236     if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
237         zipToolAvailable = true;
238 #else
239
240     QFile tool(zipToolPath);
241
242     zipToolAvailable = tool.exists();
243 #endif
244     return zipToolAvailable;
245 }
246
247 bool checkUnzipTool()
248 {
249     unzipToolAvailable = false;
250 #if defined(Q_OS_WINDOWS)
251     if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
252         zipToolAvailable = true;
253 #else
254     QFile tool(unzipToolPath);
255
256     unzipToolAvailable = tool.exists();
257 #endif
258     return unzipToolAvailable;
259 }
260
261 ErrorCode zipDir(QDir zipInputDir, QString zipName)
262 {
263     zipName = QDir::toNativeSeparators(zipName);
264     ErrorCode err = Success;
265
266     QString symLinkTarget;
267
268     QString newName;
269     // Move existing file away
270     QFile file(zipName);
271     if (file.exists()) {
272         symLinkTarget = file.symLinkTarget();
273         QString zipNameTmp = zipName + ".tmp";
274         newName = zipNameTmp;
275         int n = 0;
276         while (!file.rename(newName) && n < 5) {
277             newName =
278                 zipNameTmp + QString().setNum(n);
279             n++;
280         }
281         if (n >= 5) {
282             QMessageBox::critical(0, QObject::tr("Critical Error"),
283                                   QObject::tr("Couldn't move existing file out "
284                                               "of the way before saving."));
285             return Aborted;
286         }
287     }
288
289     // zip the temporary directory
290     VymProcess *zipProc = new VymProcess();
291     QStringList args;
292
293 #if defined(Q_OS_WINDOWS)
294     zipProc->setWorkingDirectory(
295         QDir::toNativeSeparators(zipInputDir.path() + "\\"));
296
297     args << "-a" << "-c" << "--format" << "zip" << "-f" << zipName << "*";
298
299     zipProc->start(zipToolPath, args);
300
301     if (!zipProc->waitForStarted()) {
302         // zip could not be started
303         QMessageBox::critical(
304             0, QObject::tr("Critical Error"),
305             QObject::tr("Couldn't start %1 tool to compress data!\n"
306                         "The map could not be saved, please check if "
307
308                         "backup file is available or export as XML file!")
309                     .arg("Windows zip") +
310                 "\n\nziptoolpath: " + zipToolPath +
311                 "\nargs: " + args.join(" "));
312         err = Aborted;
313     }
314     else {
315         // zip could be started
316         zipProc->waitForFinished();
317         if (zipProc->exitStatus() != QProcess::NormalExit) {
318             QMessageBox::critical(0, QObject::tr("Critical Error"),
319                                   QObject::tr("zip didn't exit normally") +
320                                       "\n" + zipProc->getErrout());
321             err = Aborted;
322         }
323         else {
324             /*
325             QMessageBox::information( 0, QObject::tr( "Debug" ),
326                                "Called:" + zipToolPath + "\n" +
327                                "Args: "  + args.join(" ") + "\n" +
328                                "Exit: "  + zipProc->exitCode() + "\n" +
329                                "Err: " + zipProc->getErrout()  + "\n" +
330                                "Std: " + zipProc->getStdout() );
331             */
332             if (zipProc->exitCode() > 1) {
333                 QMessageBox::critical(
334                     0, QObject::tr("Error"),
335                     "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
336                         "\n" + "Exit: " + zipProc->exitCode() + "\n" +
337                         "Err: " + zipProc->getErrout() + "\n" +
338                         "Std: " + zipProc->getStdout());
339                 err = Aborted;
340             }
341             else if (zipProc->exitCode() == 1) {
342                 // Non fatal according to internet, but for example
343                 // some file was locked and could not be compressed
344                 QMessageBox::warning(
345                     0, QObject::tr("Error"),
346                     "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
347                         "\n" + "Exit: " + zipProc->exitCode() + "\n" +
348                         "Err: " + zipProc->getErrout() + "\n" +
349                         "Std: " + zipProc->getStdout() +
350                         "\n"
351                         "Please check the saved map, e.g. by opening in "
352                         "another tab.\n" +
353                         "Workaround if save failed: Export as xml");
354             }
355         }
356     }
357     // qDebug() <<"Output: " << zipProc->getStdout()<<flush;
358 #else
359     zipProc->setWorkingDirectory(QDir::toNativeSeparators(zipInputDir.path()));
360     args << "-r";
361     args << zipName;
362     args << ".";
363
364     zipProc->start(zipToolPath, args);
365     if (!zipProc->waitForStarted()) {
366         // zip could not be started
367         QMessageBox::critical(
368             0, QObject::tr("Critical Error"),
369             QObject::tr("Couldn't start %1 tool to compress data!\n"
370                         "The map could not be saved, please check if "
371                         "backup file is available or export as XML file!")
372                     .arg("zip") +
373                 "\n\nziptoolpath: " + zipToolPath +
374                 "\nargs: " + args.join(" "));
375         err = Aborted;
376     }
377     else {
378         // zip could be started
379         zipProc->waitForFinished();
380         if (zipProc->exitStatus() != QProcess::NormalExit) {
381             QMessageBox::critical(0, QObject::tr("Critical Error"),
382                                   QObject::tr("zip didn't exit normally") +
383                                       "\n" + zipProc->getErrout());
384             err = Aborted;
385         }
386         else {
387             if (zipProc->exitCode() > 0) {
388                 QMessageBox::critical(
389                     0, QObject::tr("Critical Error"),
390                     QString("zip exit code:  %1").arg(zipProc->exitCode()) +
391                         "\n" + zipProc->getErrout());
392                 err = Aborted;
393             }
394         }
395     }
396 #endif
397     // Try to restore previous file, if zipping failed
398     if (err == Aborted && !newName.isEmpty() && !file.rename(zipName))
399         QMessageBox::critical(0, QObject::tr("Critical Error"),
400                               QObject::tr("Couldn't rename %1 back to %2")
401                                   .arg(newName)
402                                   .arg(zipName));
403     else {
404         // Take care of symbolic link
405         if (!symLinkTarget.isEmpty()) {
406             if (!QFile(symLinkTarget).remove()) {
407                 QMessageBox::critical(
408                     0, QObject::tr("Critical Error"),
409                     QObject::tr(
410                         "Couldn't remove target of old symbolic link %1")
411                         .arg(symLinkTarget));
412                 err = Aborted;
413                 return err;
414             }
415
416             if (!QFile(zipName).rename(symLinkTarget)) {
417                 QMessageBox::critical(
418                     0, QObject::tr("Critical Error"),
419                     QObject::tr("Couldn't rename output to target of old "
420                                 "symbolic link %1")
421                         .arg(symLinkTarget));
422                 err = Aborted;
423                 return err;
424             }
425             if (!QFile(symLinkTarget).link(zipName)) {
426                 QMessageBox::critical(
427                     0, QObject::tr("Critical Error"),
428                     QObject::tr("Couldn't link from %1 to target of old "
429                                 "symbolic link %2")
430                         .arg(zipName)
431                         .arg(symLinkTarget));
432                 err = Aborted;
433                 return err;
434             }
435         }
436
437         // Remove temporary file
438         if (!newName.isEmpty() && !file.remove())
439             QMessageBox::critical(
440                 0, QObject::tr("Critical Error"),
441                 QObject::tr("Saved %1, but couldn't remove %2")
442                     .arg(zipName)
443                     .arg(newName));
444     }
445
446     return err;
447 }
448
449 File::ErrorCode unzipDir(QDir zipOutputDir, QString zipName)
450 {
451     ErrorCode err = Success;
452
453     VymProcess *zipProc = new VymProcess();
454     QStringList args;
455
456 #if defined(Q_OS_WINDOWS)
457     zipProc->setWorkingDirectory(
458         QDir::toNativeSeparators(zipOutputDir.path() + "\\"));
459     args << "-x" << "-f" << zipName.toUtf8() << "-C" << zipOutputDir.path();
460     zipProc->start(zipToolPath, args);
461 #else
462     zipProc->setWorkingDirectory(QDir::toNativeSeparators(zipOutputDir.path()));
463     args << "-o"; // overwrite existing files!
464     args << zipName;
465     args << "-d";
466     args << zipOutputDir.path();
467
468     zipProc->start(unzipToolPath, args);
469 #endif
470     if (!zipProc->waitForStarted()) {
471         QMessageBox::critical(
472             0, QObject::tr("Critical Error"),
473             QObject::tr("Couldn't start %1 tool to decompress data!\n")
474                     .arg("Windows zip") +
475                 "\n\nziptoolpath: " + zipToolPath +
476                 "\nargs: " + args.join(" "));
477         err = Aborted;
478     }
479     else {
480         zipProc->waitForFinished();
481         if (zipProc->exitStatus() != QProcess::NormalExit) {
482             QMessageBox::critical(
483                 0, QObject::tr("Critical Error"),
484                 QObject::tr("%1 didn't exit normally").arg(zipToolPath) +
485                     zipProc->getErrout());
486             err = Aborted;
487         }
488         else {
489             /*
490             QMessageBox::information( 0, QObject::tr( "Debug" ),
491                                "Called:" + zipToolPath + "\n" +
492                                "Args: "  + args.join(" ") + "\n" +
493                                "Exit: "  + zipProc->exitCode() + "\n" +
494                                "Err: " + zipProc->getErrout()  + "\n" +
495                                "Std: " + zipProc->getStdout() );
496             */
497             if (zipProc->exitCode() > 1) {
498                 QMessageBox::critical(
499                     0, QObject::tr("Error"),
500                     "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
501                         "\n" + "Exit: " + zipProc->exitCode() + "\n" +
502                         "Err: " + zipProc->getErrout() + "\n" +
503                         "Std: " + zipProc->getStdout());
504                 err = Aborted;
505             }
506             else if (zipProc->exitCode() == 1) {
507                 // Non fatal according to internet, but for example
508                 // some file was locked and could not be compressed
509                 QMessageBox::warning(0, QObject::tr("Error"),
510                                      "Called:" + zipToolPath + "\n" +
511                                          "Args: " + args.join(" ") + "\n" +
512                                          "Exit: " + zipProc->exitCode() + "\n" +
513                                          "Err: " + zipProc->getErrout() + "\n" +
514                                          "Std: " + zipProc->getStdout() + "\n");
515             }
516         }
517     }
518     return err;
519 }
520
521 bool loadStringFromDisk(const QString &fname, QString &s)
522 {
523     s = "";
524     QFile file(fname);
525     if (!file.open(QFile::ReadOnly | QFile::Text)) {
526         qWarning() << QString("loadStringFromDisk: Cannot read file %1\n%2")
527                           .arg(fname)
528                           .arg(file.errorString());
529         return false;
530     }
531
532     QTextStream in(&file);
533     s = in.readAll();
534     return true;
535 }
536
537 bool saveStringToDisk(const QString &fname, const QString &s)
538 {
539     QFile file(fname);
540     // Write as binary (default), QFile::Text would convert linebreaks
541     if (!file.open(QFile::WriteOnly)) {
542         qWarning() << QString("saveStringToDisk: Cannot write file %1:\n%2.")
543                           .arg(fname)
544                           .arg(file.errorString());
545         return false;
546     }
547
548     QTextStream out(&file);
549     out.setCodec("UTF-8");
550     out << s;
551
552     return true;
553 }
554
555 FileType getMapType(const QString &fn)
556 {
557     int i = fn.lastIndexOf(".");
558     if (i >= 0) {
559         QString postfix = fn.mid(i + 1);
560         if (postfix == "vym" || postfix == "vyp" || postfix == "xml" ||
561             postfix == "vym~")
562             return VymMap;
563         if (postfix == "mm")
564             return FreemindMap;
565     }
566     return UnknownMap;
567 }
568
569 ImageIO::ImageIO()
570 {
571     // Create list with supported image types
572     // foreach (QByteArray format, QImageWriter::supportedImageFormats())
573     // imageTypes.append( tr("%1...").arg(QString(format).toUpper()));
574     imageFilters.append(
575         "Images (*.png *.jpg *.jpeg *.bmp *.bmp *.ppm *.xpm *.xbm)");
576     imageTypes.append("PNG");
577     imageFilters.append("Portable Network Graphics (*.png)");
578     imageTypes.append("PNG");
579     imageFilters.append("Joint Photographic Experts Group (*.jpg)");
580     imageTypes.append("JPG");
581     imageFilters.append("Joint Photographic Experts Group (*.jpeg)");
582     imageTypes.append("JPG");
583     imageFilters.append("Windows Bitmap (*.bmp)");
584     imageTypes.append("BMP");
585     imageFilters.append("Portable Pixmap (*.ppm)");
586     imageTypes.append("PPM");
587     imageFilters.append("X11 Bitmap (*.xpm)");
588     imageTypes.append("XPM");
589     imageFilters.append("X11 Bitmap (*.xbm)");
590     imageTypes.append("XBM");
591 }
592
593 QStringList ImageIO::getFilters() { return imageFilters; }
594
595 QString ImageIO::getType(QString filter)
596 {
597     for (int i = 0; i < imageFilters.count() + 1; i++)
598         if (imageFilters.at(i) == filter)
599             return imageTypes.at(i);
600     return QString();
601 }
602
603 QString ImageIO::guessType(QString fn)
604 {
605     int i = fn.lastIndexOf(".");
606     if (i >= 0) {
607         QString postfix = fn.mid(i + 1);
608         for (int i = 1; i < imageFilters.count(); i++)
609             if (imageFilters.at(i).contains(postfix))
610                 return imageTypes.at(i);
611     }
612     return QString();
613 }