X-Git-Url: https://git.sven.stormbind.net/?a=blobdiff_plain;f=src%2Ffile.cpp;fp=src%2Ffile.cpp;h=6c4b76235084b63a4287672a59c436f156d6b6e9;hb=d483bd8e6523c23c6f1d8908a2e0611c2bc9ff4f;hp=0000000000000000000000000000000000000000;hpb=7dfa3fe589d1722d49681f42cdb0bf1e6efb5223;p=sven%2Fvym.git diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 0000000..6c4b762 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,613 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "file.h" +#include "vymprocess.h" + +#if defined(Q_OS_WINDOWS) +#include "mkdtemp.h" +#include +#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::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()<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(); +}