#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QPixmap>
#include <QLabel>
#include <QTextStream>
#include <iostream>
#include <cstdlib>
#include "file.h"
#include "vymprocess.h"
#if defined(Q_OS_WIN32)
#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 reallyWriteDirectory(const QString &dir)
{
QStringList eList = QDir(dir).entryList();
if (eList.first() ==".") eList.pop_front(); // remove "."
if (eList.first() =="..") eList.pop_front(); // remove "."
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),
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) //FIXME-3 use QTemporaryDir
{
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)
{
ok = true;
QString r;
#if defined(Q_OS_WIN32)
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;
}
void 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;
}
// Traverse directories
d.setFilter( QDir::Dirs| QDir::Hidden | QDir::NoSymLinks );
QFileInfoList list = d.entryInfoList();
QFileInfo fi;
for (int i = 0; i < list.size(); ++i)
{
fi=list.at(i);
if (fi.fileName() != "." && fi.fileName() != ".." )
{
if ( !d.cd(fi.fileName()) )
qWarning ()<<"removeDir() cannot find the directory "+fi.fileName();
else
{
// Recursively remove subdirs
removeDir (d);
d.cdUp();
}
}
}
// Traverse files
d.setFilter( QDir::Files| QDir::Hidden | QDir::NoSymLinks );
list = d.entryInfoList();
for (int i = 0; i < list.size(); ++i)
{
fi=list.at(i);
QFile (fi.filePath()).remove();
}
QString dirName = d.dirName();
d.cdUp();
if (!d.rmdir(dirName))
qWarning ()<< "removeDir(" + dirName + ") failed!";
}
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;
}
void makeSubDirs (const QString &s)
{
QDir d(s);
d.mkdir(s);
d.mkdir ("images");
d.mkdir ("flags");
}
bool checkZipTool()
{
QFile tool(zipToolPath);
zipToolAvailable = tool.exists();
return zipToolAvailable;
}
bool checkUnzipTool()
{
QFile tool(unzipToolPath);
unzipToolAvailable = tool.exists();
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();
newName = zipName + ".tmp";
int n = 0;
while (!file.rename (newName) && n < 5)
{
newName = newName + QString().setNum(n); // FIXME-2 will not count
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_WIN32)
zipProc->setWorkingDirectory (QDir::toNativeSeparators(zipInputDir.path() + "\\") );
args << "a" << zipName << "-tzip" << "-scsUTF-8" << "*";
#else
zipProc->setWorkingDirectory (QDir::toNativeSeparators(zipInputDir.path() ));
args << "-r";
args << zipName;
args << ".";
#endif
zipProc->start (zipToolPath, args);
if (!zipProc->waitForStarted() )
{
// zip could not be started
QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
QObject::tr("Couldn't start tool to compress data!\n"
"The map could not be saved, please check if "
"backup file is available or export as XML file!") +
"\n\nziptool: " + zipToolPath + 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() > 0)
{
// 7z exit == 1: Non fatal according to internet, but for example
// some file was locked and could not be compressed
// Still assume critical
QMessageBox::critical( 0, QObject::tr( "Critical Error" ),
"Called:" + zipToolPath + "\n" +
"Args: " + args.join(" ") + "\n" +
"Exit: " + zipProc->exitCode() + "\n" +
"Err: " + zipProc->getErrout() + "\n" +
"Std: " + zipProc->getStdout() );
err = Aborted;
}
}
}
// 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_WIN32)
zipProc->setWorkingDirectory (QDir::toNativeSeparators(zipOutputDir.path() + "\\") );
args << "-o" + zipOutputDir.path() << "x" << zipName.toUtf8() << "-scsUTF-8";
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
{
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();
}