/* * Cantata * * Copyright (c) 2011-2013 Craig Drummond * * ---- * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "utils.h" #include "config.h" #include "mpdparseutils.h" #include "covers.h" #include "localize.h" #include #include #include #include #include #include #include #include #include #ifdef ENABLE_KDE_SUPPORT #include #endif #include #include #include #ifdef Q_OS_WIN #include #endif #ifndef Q_OS_WIN #include #include #endif #include #include QLatin1Char Utils::constDirSep('/'); QLatin1String Utils::constDirSepStr("/"); const char * Utils::constDirSepCharStr="/"; QString Utils::strippedText(QString s) { s.remove(QString::fromLatin1("...")); int i = 0; while (i < s.size()) { ++i; if (s.at(i - 1) != QLatin1Char('&')) { continue; } if (i < s.size() && s.at(i) == QLatin1Char('&')) { ++i; } s.remove(i - 1, 1); } return s.trimmed(); } QString Utils::stripAcceleratorMarkers(QString label) { int p = 0; forever { p = label.indexOf('&', p); if(p < 0 || p + 1 >= label.length()) { break; } if(label.at(p + 1).isLetterOrNumber() || label.at(p + 1) == '&') { label.remove(p, 1); } ++p; } return label; } QString Utils::fixPath(const QString &dir) { QString d(dir); if (!d.isEmpty() && !d.startsWith(QLatin1String("http://"))) { d.replace(QLatin1String("//"), constDirSepStr); } d.replace(QLatin1String("/./"), constDirSepStr); if (!d.isEmpty() && !d.endsWith(constDirSep)) { d+=constDirSep; } return d; } QString Utils::convertDirForDisplay(const QString &dir) { QString d(dir); if (d.endsWith(constDirSep)) { d=d.left(d.length()-1); } return QDir::toNativeSeparators(d); } QString Utils::convertDirFromDisplay(const QString &dir) { return fixPath(QDir::fromNativeSeparators(dir.trimmed())); } QString Utils::getDir(const QString &file) { QString d(file); int slashPos(d.lastIndexOf(constDirSep)); if(slashPos!=-1) { d.remove(slashPos+1, d.length()); } return fixPath(d); } QString Utils::getFile(const QString &file) { QString d(file); int slashPos=d.lastIndexOf(constDirSep); if (-1!=slashPos) { d.remove(0, slashPos+1); } return d; } QString Utils::changeExtension(const QString &file, const QString &extension) { if (extension.isEmpty()) { return file; } QString f(file); int pos=f.lastIndexOf('.'); if (pos>1) { f=f.left(pos+1); } if (f.endsWith('.')) { return f+(extension.startsWith('.') ? extension.mid(1) : extension); } return f+(extension.startsWith('.') ? extension : (QChar('.')+extension)); } #ifndef Q_OS_WIN gid_t Utils::getGroupId(const char *groupName) { static bool init=false; static gid_t gid=0; if (init) { return gid; } init=true; // First of all see if current group is actually 'groupName'!!! gid_t egid=getegid(); struct group *group=getgrgid(egid); if (group && 0==strcmp(group->gr_name, groupName)) { gid=egid; return gid; } // Now see if user is a member of 'groupName' struct passwd *pw=getpwuid(geteuid()); if (!pw) { return gid; } group=getgrnam(groupName); if (group) { for (int i=0; group->gr_mem[i]; ++i) { if (0==strcmp(group->gr_mem[i], pw->pw_name)) { gid=group->gr_gid; return gid; } } } return gid; } /* * Set file permissions. * If user is a memeber of "users" group, then set file as owned by and writeable by "users" group. */ void Utils::setFilePerms(const QString &file, const char *groupName) { // // Clear any umask before setting file perms mode_t oldMask(umask(0000)); gid_t gid=getGroupId(groupName); QByteArray fn=QFile::encodeName(file); ::chmod(fn.constData(), 0==gid ? 0644 : 0664); if (0!=gid) { int rv=::chown(fn.constData(), geteuid(), gid); Q_UNUSED(rv); } // Reset umask ::umask(oldMask); } #else void Utils::setFilePerms(const QString &file, const char *groupName) { Q_UNUSED(file); Q_UNUSED(groupName); } #endif /* * Create directory, and set its permissions. * If user is a memeber of "audio" group, then set dir as owned by and writeable by "audio" group. */ bool Utils::createDir(const QString &dir, const QString &base, const char *groupName) { #ifdef Q_OS_WIN Q_UNUSED(base); Q_UNUSED(groupName); #else // // Clear any umask before dir is created mode_t oldMask(umask(0000)); gid_t gid=base.isEmpty() ? 0 : getGroupId(groupName); #endif #ifdef ENABLE_KDE_SUPPORT bool status(KStandardDirs::makeDir(dir, 0==gid ? 0755 : 0775)); #else bool status(QDir(dir).mkpath(dir)); #endif #ifndef Q_OS_WIN if (status && 0!=gid && dir.startsWith(base)) { QStringList parts=dir.mid(base.length()).split(constDirSep); QString d(base); foreach (const QString &p, parts) { d+=constDirSep+p; int rv=::chown(QFile::encodeName(d).constData(), geteuid(), gid); Q_UNUSED(rv); } } // Reset umask ::umask(oldMask); #endif return status; } struct Thread : public QThread { using QThread::msleep; }; void Utils::msleep(int msecs) { Thread::msleep(msecs); } #ifndef ENABLE_KDE_SUPPORT // Copied from KDE... START #include QString Utils::formatByteSize(double size) { static bool useSiUnites=false; static QLocale locale; #ifndef Q_OS_WIN static bool init=false; if (!init) { init=true; const char *env=qgetenv("KDE_FULL_SESSION"); QString dm=env && 0==strcmp(env, "true") ? QLatin1String("KDE") : QString(qgetenv("XDG_CURRENT_DESKTOP")); useSiUnites=!dm.isEmpty() && QLatin1String("KDE")!=dm; } #endif int unit = 0; double multiplier = useSiUnites ? 1000.0 : 1024.0; while (qAbs(size) >= multiplier && unit < 3) { size /= multiplier; unit++; } if (useSiUnites) { switch(unit) { case 0: return i18n("%1 B", size); case 1: return i18n("%1 kB", locale.toString(size, 'f', 1)); case 2: return i18n("%1 MB", locale.toString(size, 'f', 1)); default: case 3: return i18n("%1 GB", locale.toString(size, 'f', 1)); } } else { switch(unit) { case 0: return i18n("%1 B", size); case 1: return i18n("%1 KiB", locale.toString(size, 'f', 1)); case 2: return i18n("%1 MiB", locale.toString(size, 'f', 1)); default: case 3: return i18n("%1 GiB", locale.toString(size, 'f', 1)); } } } #if defined Q_OS_WIN #define KPATH_SEPARATOR ';' // #define KDIR_SEPARATOR '\\' /* faster than QDir::separator() */ #else #define KPATH_SEPARATOR ':' // #define KDIR_SEPARATOR '/' /* faster than QDir::separator() */ #endif static inline QString equalizePath(QString &str) { #ifdef Q_OS_WIN // filter pathes through QFileInfo to have always // the same case for drive letters QFileInfo f(str); if (f.isAbsolute()) return f.absoluteFilePath(); else #endif return str; } static void tokenize(QStringList &tokens, const QString &str, const QString &delim) { const int len = str.length(); QString token; for(int index = 0; index < len; index++) { if (delim.contains(str[index])) { tokens.append(equalizePath(token)); token.clear(); } else { token += str[index]; } } if (!token.isEmpty()) { tokens.append(equalizePath(token)); } } #ifdef Q_OS_WIN static QStringList executableExtensions() { QStringList ret = QString::fromLocal8Bit(qgetenv("PATHEXT")).split(QLatin1Char(';')); if (!ret.contains(QLatin1String(".exe"), Qt::CaseInsensitive)) { // If %PATHEXT% does not contain .exe, it is either empty, malformed, or distorted in ways that we cannot support, anyway. ret.clear(); ret << QLatin1String(".exe") << QLatin1String(".com") << QLatin1String(".bat") << QLatin1String(".cmd"); } return ret; } #endif static QStringList systemPaths(const QString &pstr) { QStringList tokens; QString p = pstr; if( p.isEmpty() ) { p = QString::fromLocal8Bit( qgetenv( "PATH" ) ); } QString delimiters(QLatin1Char(KPATH_SEPARATOR)); delimiters += QLatin1Char('\b'); tokenize( tokens, p, delimiters ); QStringList exePaths; // split path using : or \b as delimiters for( int i = 0; i < tokens.count(); i++ ) { exePaths << /*KShell::tildeExpand(*/ tokens[ i ] /*)*/; // TODO } return exePaths; } #ifdef Q_WS_MAC static QString getBundle(const QString &path) { //kDebug(180) << "getBundle(" << path << ", " << ignore << ") called"; QFileInfo info; QString bundle = path; bundle += QLatin1String(".app/Contents/MacOS/") + bundle.section(QLatin1Char('/'), -1); info.setFile( bundle ); FILE *file; if (file = fopen(info.absoluteFilePath().toUtf8().constData(), "r")) { fclose(file); struct stat _stat; if ((stat(info.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( _stat.st_mode & S_IXUSR ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { //kDebug(180) << "getBundle(): returning " << bundle; return bundle; } } } return QString(); } #endif static QString checkExecutable( const QString& path ) { #ifdef Q_WS_MAC QString bundle = getBundle( path ); if ( !bundle.isEmpty() ) { //kDebug(180) << "findExe(): returning " << bundle; return bundle; } #endif QFileInfo info( path ); QFileInfo orig = info; #if defined(Q_OS_DARWIN) || defined(Q_OS_MAC) FILE *file; if (file = fopen(orig.absoluteFilePath().toUtf8().constData(), "r")) { fclose(file); struct stat _stat; if ((stat(orig.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( _stat.st_mode & S_IXUSR ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { orig.makeAbsolute(); return orig.filePath(); } } } return QString(); #else if( info.exists() && info.isSymLink() ) info = QFileInfo( info.canonicalFilePath() ); if( info.exists() && info.isExecutable() && info.isFile() ) { // return absolute path, but without symlinks resolved in order to prevent // problems with executables that work differently depending on name they are // run as (for example gunzip) orig.makeAbsolute(); return orig.filePath(); } //kDebug(180) << "checkExecutable(): failed, returning empty string"; return QString(); #endif } QString Utils::findExe(const QString &appname, const QString &pstr) { #ifdef Q_OS_WIN QStringList executable_extensions = executableExtensions(); if (!executable_extensions.contains(appname.section(QLatin1Char('.'), -1, -1, QString::SectionIncludeLeadingSep), Qt::CaseInsensitive)) { QString found_exe; foreach (const QString& extension, executable_extensions) { found_exe = findExe(appname + extension, pstr); if (!found_exe.isEmpty()) { return found_exe; } } return QString(); } #endif const QStringList exePaths = systemPaths( pstr ); for (QStringList::ConstIterator it = exePaths.begin(); it != exePaths.end(); ++it) { QString p = (*it) + QLatin1Char('/'); p += appname; QString result = checkExecutable(p); if (!result.isEmpty()) { return result; } } return QString(); } // Copied from KDE... END #endif QString Utils::cleanPath(const QString &p) { QString path(p); while (path.contains("//")) { path.replace("//", constDirSepStr); } return fixPath(path); } static QString userDir(const QString &mainDir, const QString &sub, bool create) { QString dir=mainDir; if (!sub.isEmpty()) { dir+=sub; } dir=Utils::cleanPath(dir); QDir d(dir); return d.exists() || (create && d.mkpath(dir)) ? dir : QString(); } QString Utils::configDir(const QString &sub, bool create) { #if defined Q_OS_WIN return userDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation)+constDirSep, sub, create); #else QString env = qgetenv("XDG_CONFIG_HOME"); return userDir((env.isEmpty() ? QDir::homePath() + "/.config" : env) + constDirSep+QCoreApplication::applicationName()+constDirSep, sub, create); #endif } QString Utils::dataDir(const QString &sub, bool create) { #if defined Q_OS_WIN return userDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation)+constDirSep, sub, create); #else QString env = qgetenv("XDG_DATA_HOME"); return userDir((env.isEmpty() ? QDir::homePath() + "/.local/share" : env) + constDirSep+QCoreApplication::applicationName()+constDirSep, sub, create); #endif } QString Utils::cacheDir(const QString &sub, bool create) { #if defined Q_OS_WIN return userDir(QDesktopServices::storageLocation(QDesktopServices::CacheLocation)+constDirSep, sub, create); #else QString env = qgetenv("XDG_CACHE_HOME"); return userDir((env.isEmpty() ? QDir::homePath() + "/.cache" : env) + constDirSep+QCoreApplication::applicationName()+constDirSep, sub, create); #endif } void Utils::moveFile(const QString &from, const QString &to) { if (!from.isEmpty() && !to.isEmpty() && from!=to && QFile::exists(from) && !QFile::exists(to)) { QFile::rename(from, to); } } void Utils::moveDir(const QString &from, const QString &to) { if (from.isEmpty() || to.isEmpty() || from==to) { return; } QDir f(from); if (!f.exists()) { return; } QDir t(to); if (!t.exists()) { return; } QFileInfoList files=f.entryInfoList(QStringList() << "*", QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); foreach (const QFileInfo &file, files) { if (file.isDir()) { QString dest=to+file.fileName()+constDirSep; if (!QDir(dest).exists()) { t.mkdir(file.fileName()); } moveDir(from+file.fileName()+constDirSep, dest); } else { QFile::rename(from+file.fileName(), to+file.fileName()); } } f.cdUp(); f.rmdir(from); } void Utils::clearOldCache(const QString &sub, int maxAge) { if (sub.isEmpty()) { return; } QString d=cacheDir(sub, false); if (d.isEmpty()) { return; } QDir dir(d); if (dir.exists()) { QFileInfoList files=dir.entryInfoList(QDir::Files|QDir::NoDotAndDotDot); if (files.count()) { QDateTime now=QDateTime::currentDateTime(); foreach (const QFileInfo &f, files) { if (f.lastModified().daysTo(now)>maxAge) { QFile::remove(f.absoluteFilePath()); } } } } } void Utils::touchFile(const QString &fileName) { ::utime(QFile::encodeName(fileName).constData(), 0); } extern int Utils::layoutSpacing(QWidget *w) { int spacing=(w ? w->style() : qApp->style())->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical); if (spacing<0) { spacing=QApplication::fontMetrics().height()>20 ? 8 : 4; } return spacing; }