]> git.sven.stormbind.net Git - sven/vym.git/blobdiff - src/file.cpp
New upstream version 2.9.22
[sven/vym.git] / src / file.cpp
diff --git a/src/file.cpp b/src/file.cpp
new file mode 100644 (file)
index 0000000..6c4b762
--- /dev/null
@@ -0,0 +1,613 @@
+#include <QDebug>
+#include <QDir>
+#include <QLabel>
+#include <QMessageBox>
+#include <QOperatingSystemVersion>
+#include <QPixmap>
+#include <QTextStream>
+#include <cstdlib>
+#include <iostream>
+
+#include "file.h"
+#include "vymprocess.h"
+
+#if defined(Q_OS_WINDOWS)
+#include "mkdtemp.h"
+#include <windows.h>
+#endif
+
+#if defined(Q_OS_MACX)
+#include "unistd.h"
+#endif
+
+using namespace File;
+
+extern QString zipToolPath;
+extern QString unzipToolPath;
+extern bool zipToolAvailable;
+extern bool unzipToolAvailable;
+
+QString convertToRel(const QString &src, const QString &dst)
+{
+    // Creates a relative path pointing from src to dst
+
+    QString s = src;
+    QString d = dst;
+    int i;
+
+    if (s == d) {
+        // Special case, we just need the name of the file,
+        // not the complete path
+        i = d.lastIndexOf("/");
+        d = d.right(d.length() - i - 1);
+    }
+    else {
+        // remove identical left parts
+        while (s.section("/", 0, 0) == d.section("/", 0, 0)) {
+            i = s.indexOf("/");
+            s = s.right(s.length() - i - 1);
+            d = d.right(d.length() - i - 1);
+        }
+
+        // Now take care of paths where we have to go back first
+        int srcsep = s.count("/");
+        while (srcsep > 0) {
+            d = "../" + d;
+            srcsep--;
+        }
+    }
+    return d;
+}
+
+QString convertToAbs(const QString &src, const QString &dst)
+{
+    // Creates a relative path pointing from src to dst
+    QDir dd(src);
+    return dd.absoluteFilePath(dst);
+}
+
+QString basename(const QString &path) { return path.section('/', -1); }
+
+QString dirname(const QString &path) { return path.section('/', 0, -2); }
+
+extern QString vymName;
+bool confirmDirectoryOverwrite(const QDir &dir)
+{
+    if (!dir.exists()) {
+        qWarning() << "Directory does not exist: " << dir.path();
+        return false;
+    }
+
+    QStringList eList = dir.entryList();
+    while (!eList.isEmpty() && (eList.first() == "." || eList.first() == ".."))
+        eList.pop_front(); // remove "." and ".."
+
+    if (!eList.isEmpty()) {
+        QMessageBox mb(vymName,
+                       QObject::tr("The directory %1 is not empty.\nDo you "
+                                   "risk to overwrite its contents?",
+                                   "write directory")
+                           .arg(dir.path()),
+                       QMessageBox::Warning, QMessageBox::Yes,
+                       QMessageBox::Cancel | QMessageBox::Default,
+                       QMessageBox::NoButton);
+
+        mb.setButtonText(QMessageBox::Yes, QObject::tr("Overwrite"));
+        mb.setButtonText(QMessageBox::No, QObject::tr("Cancel"));
+        switch (mb.exec()) {
+        case QMessageBox::Yes:
+            // save
+            return true;
+        case QMessageBox::Cancel:
+            // do nothing
+            return false;
+        }
+    }
+    return true;
+}
+
+QString makeTmpDir(bool &ok, const QString &dirPath,
+                   const QString &prefix)
+{
+    QString path = makeUniqueDir(ok, dirPath + "/" + prefix + "-XXXXXX");
+    return path;
+}
+
+QString makeTmpDir(bool &ok, const QString &prefix)
+{
+    return makeTmpDir(ok, QDir::tempPath(), prefix);
+}
+
+bool isInTmpDir(QString fn)
+{
+    QString temp = QDir::tempPath();
+    int l = temp.length();
+    return fn.left(l) == temp;
+}
+
+QString makeUniqueDir(bool &ok, QString s) // FIXME-3 use QTemporaryDir
+{
+    ok = true;
+
+    QString r;
+
+#if defined(Q_OS_WINDOWS)
+    r = mkdtemp(s);
+#else
+    // On Linux and friends use cstdlib
+
+    // Convert QString to string
+    ok = true;
+    char *p;
+    int bytes = s.length();
+    p = (char *)malloc(bytes + 1);
+    int i;
+    for (i = 0; i < bytes; i++)
+        p[i] = s.at(i).unicode();
+    p[bytes] = 0;
+
+    r = mkdtemp(p);
+    free(p);
+#endif
+
+    if (r.isEmpty())
+        ok = false;
+    return r;
+}
+
+bool removeDir(QDir d)
+{
+    // This check should_ not be necessary, but proved to be useful ;-)
+    if (!isInTmpDir(d.path())) {
+        qWarning() << "file.cpp::removeDir should remove " + d.path() +
+                          " - aborted.";
+        return false;
+    }
+
+    return d.removeRecursively();
+}
+
+bool copyDir(QDir src, QDir dst, const bool &override)
+{
+    QStringList dirs =
+        src.entryList(QDir::AllDirs | QDir::Hidden | QDir::NoDotAndDotDot);
+    QStringList files = src.entryList(QDir::Files);
+
+    // Check if dst is a subdir of src, which would cause endless recursion...
+    if (dst.absolutePath().contains(src.absolutePath()))
+        return false;
+
+    // Traverse directories
+    QList<QString>::iterator d, f;
+    for (d = dirs.begin(); d != dirs.end(); ++d) {
+        if (!QFileInfo(src.path() + "/" + (*d)).isDir())
+            continue;
+
+        QDir cdir(dst.path() + "/" + (*d));
+        cdir.mkpath(cdir.path());
+
+        if (!copyDir(QDir(src.path() + "/" + (*d)),
+                     QDir(dst.path() + "/" + (*d)), override))
+            return false;
+    }
+
+    // Traverse files
+    for (f = files.begin(); f != files.end(); ++f) {
+        QFile cfile(src.path() + "/" + (*f));
+        QFile destFile(dst.path() + "/" +
+                       src.relativeFilePath(cfile.fileName()));
+        if (destFile.exists() && override)
+            destFile.remove();
+
+        if (!cfile.copy(dst.path() + "/" +
+                        src.relativeFilePath(cfile.fileName())))
+            return false;
+    }
+    return true;
+}
+
+bool subDirsExist()
+{
+    QStringList dirList;
+    dirList << "images";
+    dirList << "flags";
+    dirList << "flags/user";
+    dirList << "flags/standard";
+    foreach (QString d, dirList)
+        if (QDir(d).exists() ) return true;
+
+    return false;
+}
+
+void makeSubDirs(const QString &s)
+{
+    QDir d(s);
+    d.mkdir(s);
+    d.mkdir("images");
+    d.mkdir("flags");
+    d.mkdir("flags/user");
+    d.mkdir("flags/standard");
+}
+
+bool checkZipTool()
+{
+    zipToolAvailable = false;
+#if defined(Q_OS_WINDOWS)
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
+        zipToolAvailable = true;
+#else
+
+    QFile tool(zipToolPath);
+
+    zipToolAvailable = tool.exists();
+#endif
+    return zipToolAvailable;
+}
+
+bool checkUnzipTool()
+{
+    unzipToolAvailable = false;
+#if defined(Q_OS_WINDOWS)
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
+        zipToolAvailable = true;
+#else
+    QFile tool(unzipToolPath);
+
+    unzipToolAvailable = tool.exists();
+#endif
+    return unzipToolAvailable;
+}
+
+ErrorCode zipDir(QDir zipInputDir, QString zipName)
+{
+    zipName = QDir::toNativeSeparators(zipName);
+    ErrorCode err = Success;
+
+    QString symLinkTarget;
+
+    QString newName;
+    // Move existing file away
+    QFile file(zipName);
+    if (file.exists()) {
+        symLinkTarget = file.symLinkTarget();
+        QString zipNameTmp = zipName + ".tmp";
+        newName = zipNameTmp;
+        int n = 0;
+        while (!file.rename(newName) && n < 5) {
+            newName =
+                zipNameTmp + QString().setNum(n);
+            n++;
+        }
+        if (n >= 5) {
+            QMessageBox::critical(0, QObject::tr("Critical Error"),
+                                  QObject::tr("Couldn't move existing file out "
+                                              "of the way before saving."));
+            return Aborted;
+        }
+    }
+
+    // zip the temporary directory
+    VymProcess *zipProc = new VymProcess();
+    QStringList args;
+
+#if defined(Q_OS_WINDOWS)
+    zipProc->setWorkingDirectory(
+        QDir::toNativeSeparators(zipInputDir.path() + "\\"));
+
+    args << "-a" << "-c" << "--format" << "zip" << "-f" << zipName << "*";
+
+    zipProc->start(zipToolPath, args);
+
+    if (!zipProc->waitForStarted()) {
+        // zip could not be started
+        QMessageBox::critical(
+            0, QObject::tr("Critical Error"),
+            QObject::tr("Couldn't start %1 tool to compress data!\n"
+                        "The map could not be saved, please check if "
+
+                        "backup file is available or export as XML file!")
+                    .arg("Windows zip") +
+                "\n\nziptoolpath: " + zipToolPath +
+                "\nargs: " + args.join(" "));
+        err = Aborted;
+    }
+    else {
+        // zip could be started
+        zipProc->waitForFinished();
+        if (zipProc->exitStatus() != QProcess::NormalExit) {
+            QMessageBox::critical(0, QObject::tr("Critical Error"),
+                                  QObject::tr("zip didn't exit normally") +
+                                      "\n" + zipProc->getErrout());
+            err = Aborted;
+        }
+        else {
+            /*
+            QMessageBox::information( 0, QObject::tr( "Debug" ),
+                               "Called:" + zipToolPath + "\n" +
+                               "Args: "  + args.join(" ") + "\n" +
+                               "Exit: "  + zipProc->exitCode() + "\n" +
+                               "Err: " + zipProc->getErrout()  + "\n" +
+                               "Std: " + zipProc->getStdout() );
+            */
+            if (zipProc->exitCode() > 1) {
+                QMessageBox::critical(
+                    0, QObject::tr("Error"),
+                    "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
+                        "\n" + "Exit: " + zipProc->exitCode() + "\n" +
+                        "Err: " + zipProc->getErrout() + "\n" +
+                        "Std: " + zipProc->getStdout());
+                err = Aborted;
+            }
+            else if (zipProc->exitCode() == 1) {
+                // Non fatal according to internet, but for example
+                // some file was locked and could not be compressed
+                QMessageBox::warning(
+                    0, QObject::tr("Error"),
+                    "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
+                        "\n" + "Exit: " + zipProc->exitCode() + "\n" +
+                        "Err: " + zipProc->getErrout() + "\n" +
+                        "Std: " + zipProc->getStdout() +
+                        "\n"
+                        "Please check the saved map, e.g. by opening in "
+                        "another tab.\n" +
+                        "Workaround if save failed: Export as xml");
+            }
+        }
+    }
+    // qDebug() <<"Output: " << zipProc->getStdout()<<flush;
+#else
+    zipProc->setWorkingDirectory(QDir::toNativeSeparators(zipInputDir.path()));
+    args << "-r";
+    args << zipName;
+    args << ".";
+
+    zipProc->start(zipToolPath, args);
+    if (!zipProc->waitForStarted()) {
+        // zip could not be started
+        QMessageBox::critical(
+            0, QObject::tr("Critical Error"),
+            QObject::tr("Couldn't start %1 tool to compress data!\n"
+                        "The map could not be saved, please check if "
+                        "backup file is available or export as XML file!")
+                    .arg("zip") +
+                "\n\nziptoolpath: " + zipToolPath +
+                "\nargs: " + args.join(" "));
+        err = Aborted;
+    }
+    else {
+        // zip could be started
+        zipProc->waitForFinished();
+        if (zipProc->exitStatus() != QProcess::NormalExit) {
+            QMessageBox::critical(0, QObject::tr("Critical Error"),
+                                  QObject::tr("zip didn't exit normally") +
+                                      "\n" + zipProc->getErrout());
+            err = Aborted;
+        }
+        else {
+            if (zipProc->exitCode() > 0) {
+                QMessageBox::critical(
+                    0, QObject::tr("Critical Error"),
+                    QString("zip exit code:  %1").arg(zipProc->exitCode()) +
+                        "\n" + zipProc->getErrout());
+                err = Aborted;
+            }
+        }
+    }
+#endif
+    // Try to restore previous file, if zipping failed
+    if (err == Aborted && !newName.isEmpty() && !file.rename(zipName))
+        QMessageBox::critical(0, QObject::tr("Critical Error"),
+                              QObject::tr("Couldn't rename %1 back to %2")
+                                  .arg(newName)
+                                  .arg(zipName));
+    else {
+        // Take care of symbolic link
+        if (!symLinkTarget.isEmpty()) {
+            if (!QFile(symLinkTarget).remove()) {
+                QMessageBox::critical(
+                    0, QObject::tr("Critical Error"),
+                    QObject::tr(
+                        "Couldn't remove target of old symbolic link %1")
+                        .arg(symLinkTarget));
+                err = Aborted;
+                return err;
+            }
+
+            if (!QFile(zipName).rename(symLinkTarget)) {
+                QMessageBox::critical(
+                    0, QObject::tr("Critical Error"),
+                    QObject::tr("Couldn't rename output to target of old "
+                                "symbolic link %1")
+                        .arg(symLinkTarget));
+                err = Aborted;
+                return err;
+            }
+            if (!QFile(symLinkTarget).link(zipName)) {
+                QMessageBox::critical(
+                    0, QObject::tr("Critical Error"),
+                    QObject::tr("Couldn't link from %1 to target of old "
+                                "symbolic link %2")
+                        .arg(zipName)
+                        .arg(symLinkTarget));
+                err = Aborted;
+                return err;
+            }
+        }
+
+        // Remove temporary file
+        if (!newName.isEmpty() && !file.remove())
+            QMessageBox::critical(
+                0, QObject::tr("Critical Error"),
+                QObject::tr("Saved %1, but couldn't remove %2")
+                    .arg(zipName)
+                    .arg(newName));
+    }
+
+    return err;
+}
+
+File::ErrorCode unzipDir(QDir zipOutputDir, QString zipName)
+{
+    ErrorCode err = Success;
+
+    VymProcess *zipProc = new VymProcess();
+    QStringList args;
+
+#if defined(Q_OS_WINDOWS)
+    zipProc->setWorkingDirectory(
+        QDir::toNativeSeparators(zipOutputDir.path() + "\\"));
+    args << "-x" << "-f" << zipName.toUtf8() << "-C" << zipOutputDir.path();
+    zipProc->start(zipToolPath, args);
+#else
+    zipProc->setWorkingDirectory(QDir::toNativeSeparators(zipOutputDir.path()));
+    args << "-o"; // overwrite existing files!
+    args << zipName;
+    args << "-d";
+    args << zipOutputDir.path();
+
+    zipProc->start(unzipToolPath, args);
+#endif
+    if (!zipProc->waitForStarted()) {
+        QMessageBox::critical(
+            0, QObject::tr("Critical Error"),
+            QObject::tr("Couldn't start %1 tool to decompress data!\n")
+                    .arg("Windows zip") +
+                "\n\nziptoolpath: " + zipToolPath +
+                "\nargs: " + args.join(" "));
+        err = Aborted;
+    }
+    else {
+        zipProc->waitForFinished();
+        if (zipProc->exitStatus() != QProcess::NormalExit) {
+            QMessageBox::critical(
+                0, QObject::tr("Critical Error"),
+                QObject::tr("%1 didn't exit normally").arg(zipToolPath) +
+                    zipProc->getErrout());
+            err = Aborted;
+        }
+        else {
+            /*
+            QMessageBox::information( 0, QObject::tr( "Debug" ),
+                               "Called:" + zipToolPath + "\n" +
+                               "Args: "  + args.join(" ") + "\n" +
+                               "Exit: "  + zipProc->exitCode() + "\n" +
+                               "Err: " + zipProc->getErrout()  + "\n" +
+                               "Std: " + zipProc->getStdout() );
+            */
+            if (zipProc->exitCode() > 1) {
+                QMessageBox::critical(
+                    0, QObject::tr("Error"),
+                    "Called:" + zipToolPath + "\n" + "Args: " + args.join(" ") +
+                        "\n" + "Exit: " + zipProc->exitCode() + "\n" +
+                        "Err: " + zipProc->getErrout() + "\n" +
+                        "Std: " + zipProc->getStdout());
+                err = Aborted;
+            }
+            else if (zipProc->exitCode() == 1) {
+                // Non fatal according to internet, but for example
+                // some file was locked and could not be compressed
+                QMessageBox::warning(0, QObject::tr("Error"),
+                                     "Called:" + zipToolPath + "\n" +
+                                         "Args: " + args.join(" ") + "\n" +
+                                         "Exit: " + zipProc->exitCode() + "\n" +
+                                         "Err: " + zipProc->getErrout() + "\n" +
+                                         "Std: " + zipProc->getStdout() + "\n");
+            }
+        }
+    }
+    return err;
+}
+
+bool loadStringFromDisk(const QString &fname, QString &s)
+{
+    s = "";
+    QFile file(fname);
+    if (!file.open(QFile::ReadOnly | QFile::Text)) {
+        qWarning() << QString("loadStringFromDisk: Cannot read file %1\n%2")
+                          .arg(fname)
+                          .arg(file.errorString());
+        return false;
+    }
+
+    QTextStream in(&file);
+    s = in.readAll();
+    return true;
+}
+
+bool saveStringToDisk(const QString &fname, const QString &s)
+{
+    QFile file(fname);
+    // Write as binary (default), QFile::Text would convert linebreaks
+    if (!file.open(QFile::WriteOnly)) {
+        qWarning() << QString("saveStringToDisk: Cannot write file %1:\n%2.")
+                          .arg(fname)
+                          .arg(file.errorString());
+        return false;
+    }
+
+    QTextStream out(&file);
+    out.setCodec("UTF-8");
+    out << s;
+
+    return true;
+}
+
+FileType getMapType(const QString &fn)
+{
+    int i = fn.lastIndexOf(".");
+    if (i >= 0) {
+        QString postfix = fn.mid(i + 1);
+        if (postfix == "vym" || postfix == "vyp" || postfix == "xml" ||
+            postfix == "vym~")
+            return VymMap;
+        if (postfix == "mm")
+            return FreemindMap;
+    }
+    return UnknownMap;
+}
+
+ImageIO::ImageIO()
+{
+    // Create list with supported image types
+    // foreach (QByteArray format, QImageWriter::supportedImageFormats())
+    // imageTypes.append( tr("%1...").arg(QString(format).toUpper()));
+    imageFilters.append(
+        "Images (*.png *.jpg *.jpeg *.bmp *.bmp *.ppm *.xpm *.xbm)");
+    imageTypes.append("PNG");
+    imageFilters.append("Portable Network Graphics (*.png)");
+    imageTypes.append("PNG");
+    imageFilters.append("Joint Photographic Experts Group (*.jpg)");
+    imageTypes.append("JPG");
+    imageFilters.append("Joint Photographic Experts Group (*.jpeg)");
+    imageTypes.append("JPG");
+    imageFilters.append("Windows Bitmap (*.bmp)");
+    imageTypes.append("BMP");
+    imageFilters.append("Portable Pixmap (*.ppm)");
+    imageTypes.append("PPM");
+    imageFilters.append("X11 Bitmap (*.xpm)");
+    imageTypes.append("XPM");
+    imageFilters.append("X11 Bitmap (*.xbm)");
+    imageTypes.append("XBM");
+}
+
+QStringList ImageIO::getFilters() { return imageFilters; }
+
+QString ImageIO::getType(QString filter)
+{
+    for (int i = 0; i < imageFilters.count() + 1; i++)
+        if (imageFilters.at(i) == filter)
+            return imageTypes.at(i);
+    return QString();
+}
+
+QString ImageIO::guessType(QString fn)
+{
+    int i = fn.lastIndexOf(".");
+    if (i >= 0) {
+        QString postfix = fn.mid(i + 1);
+        for (int i = 1; i < imageFilters.count(); i++)
+            if (imageFilters.at(i).contains(postfix))
+                return imageTypes.at(i);
+    }
+    return QString();
+}