File indexing completed on 2024-05-19 04:56:13

0001 /**
0002  * \file coreplatformtools.cpp
0003  * Core platform specific tools for Qt.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 10 Aug 2013
0008  *
0009  * Copyright (C) 2013-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "coreplatformtools.h"
0028 #include <QFileInfo>
0029 #include <QSettings>
0030 #include <QCoreApplication>
0031 #include "config.h"
0032 #include "kid3settings.h"
0033 #include "coretaggedfileiconprovider.h"
0034 
0035 /**
0036  * Constructor.
0037  */
0038 CorePlatformTools::CorePlatformTools()
0039   : m_settings(nullptr)
0040 {
0041 }
0042 
0043 /**
0044  * Destructor.
0045  */
0046 CorePlatformTools::~CorePlatformTools()
0047 {
0048   // Must not be inline because of forwared declared QScopedPointer.
0049 }
0050 
0051 /**
0052  * Get application settings.
0053  * @return settings instance.
0054  */
0055 ISettings* CorePlatformTools::applicationSettings()
0056 {
0057   if (!m_config) {
0058     if (QByteArray configPath = qgetenv("KID3_CONFIG_FILE");
0059         configPath.isNull()) {
0060       m_settings = new QSettings(
0061             QSettings::UserScope, QLatin1String("Kid3"),
0062             QLatin1String("Kid3"), qApp);
0063     } else {
0064       m_settings = new QSettings(
0065             QFile::decodeName(configPath), QSettings::IniFormat, qApp);
0066     }
0067     m_config.reset(new Kid3Settings(m_settings));
0068   }
0069   return m_config.data();
0070 }
0071 
0072 /**
0073  * Get icon provider for tagged files.
0074  * @return icon provider.
0075  */
0076 CoreTaggedFileIconProvider* CorePlatformTools::iconProvider()
0077 {
0078   if (!m_iconProvider) {
0079     m_iconProvider.reset(new CoreTaggedFileIconProvider);
0080   }
0081   return m_iconProvider.data();
0082 }
0083 
0084 /**
0085  * Write text to clipboard.
0086  * @param text text to write
0087  * @return true if operation is supported.
0088  */
0089 bool CorePlatformTools::writeToClipboard(const QString& text) const
0090 {
0091   Q_UNUSED(text)
0092   return false;
0093 }
0094 
0095 /**
0096  * Read text from clipboard.
0097  * @return text, null if operation not supported.
0098  */
0099 QString CorePlatformTools::readFromClipboard() const
0100 {
0101   return QString();
0102 }
0103 
0104 /**
0105  * Create an audio player instance.
0106  * @param app application context
0107  * @param dbusEnabled true to enable MPRIS D-Bus interface
0108  * @return audio player, nullptr if not supported.
0109  */
0110 QObject* CorePlatformTools::createAudioPlayer(Kid3Application* app,
0111                                               bool dbusEnabled) const
0112 {
0113   Q_UNUSED(app)
0114   Q_UNUSED(dbusEnabled)
0115   return nullptr;
0116 }
0117 
0118 #ifdef Q_OS_WIN32
0119 
0120 #include <QVector>
0121 #ifdef Q_CC_MSVC
0122 #include <windows.h>
0123 #else
0124 #include <windef.h>
0125 #include <winbase.h>
0126 #include <shellapi.h>
0127 #endif
0128 
0129 bool CorePlatformTools::moveToTrash(const QString& path) const
0130 {
0131   typedef int (WINAPI *SHFileOperationW_t)(LPSHFILEOPSTRUCTW);
0132   HMODULE hshell32 = GetModuleHandleA("shell32.dll");
0133   SHFileOperationW_t pSHFileOperationW = reinterpret_cast<SHFileOperationW_t>(
0134         GetProcAddress(hshell32, "SHFileOperationW"));
0135   if (!pSHFileOperationW) {
0136     // SHFileOperationW is only available since Windows XP.
0137     return false;
0138   }
0139 
0140   QFileInfo fi(path);
0141   const QString absPath(fi.absoluteFilePath());
0142 
0143   QVector<WCHAR> from(absPath.length() + 2);
0144   int i;
0145   for (i = 0; i < absPath.length(); i++) {
0146     from[i] = absPath.at(i).unicode();
0147   }
0148   from[i++] = 0;
0149   from[i++] = 0;
0150 
0151   SHFILEOPSTRUCTW fileOp;
0152   fileOp.hwnd = 0;
0153   fileOp.wFunc = FO_DELETE;
0154   fileOp.pFrom = from.data();
0155   fileOp.pTo = 0;
0156   fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
0157   fileOp.fAnyOperationsAborted = 0;
0158   fileOp.hNameMappings = 0;
0159   fileOp.lpszProgressTitle = 0;
0160   return pSHFileOperationW(&fileOp) == 0;
0161 }
0162 
0163 #elif defined Q_OS_MAC
0164 
0165 #include <CoreServices/CoreServices.h>
0166 
0167 bool CorePlatformTools::moveToTrash(const QString& path) const
0168 {
0169   QFileInfo fi(path);
0170   const QString absPath(fi.absoluteFilePath());
0171   FSRef fsRef;
0172   OSErr err = FSPathMakeRefWithOptions(
0173     reinterpret_cast<const UInt8*>(
0174       QFile::encodeName(absPath).constData()),
0175     kFSPathMakeRefDoNotFollowLeafSymlink, &fsRef, 0);
0176   if (err != noErr)
0177     return false;
0178 
0179   return FSMoveObjectToTrashSync(&fsRef, 0, kFSFileOperationDefaultOptions) == noErr;
0180 }
0181 
0182 #else
0183 
0184 /*
0185  * Implemented according to Desktop Trash Can Specification at
0186  * http://www.freedesktop.org/wiki/Specifications/trash-spec
0187  */
0188 
0189 #include <QDir>
0190 #include <QDateTime>
0191 #include <QTextStream>
0192 #include <QUrl>
0193 #include <cstdlib>
0194 #include <cstdio>
0195 #include <sys/types.h>
0196 #include <sys/stat.h>
0197 #include <unistd.h>
0198 #ifdef HAVE_MNTENT_H
0199 #include <mntent.h>
0200 #endif
0201 
0202 namespace {
0203 
0204 bool moveToTrashDir(const QFileInfo& fi, const QString& trashDir)
0205 {
0206   QString absPath(fi.absoluteFilePath());
0207   QString fileName(fi.fileName());
0208   QString filesPath(trashDir + QLatin1String("/files"));
0209   QString infoPath(trashDir + QLatin1String("/info"));
0210   QString baseName(fi.baseName());
0211   QString suffix(fi.completeSuffix());
0212   QString destName(fileName);
0213   int counter = 1;
0214   while (QFile::exists(filesPath + QLatin1Char('/') + destName) ||
0215          QFile::exists(infoPath + QLatin1Char('/') + destName +
0216                        QLatin1String(".trashinfo"))) {
0217     ++counter;
0218     destName = QString(QLatin1String("%1.%2.%3"))
0219         .arg(baseName).arg(counter).arg(suffix);
0220   }
0221   if (!(QDir(filesPath).exists() ||
0222         QDir().mkpath(filesPath)) ||
0223       !(QDir(infoPath).exists() ||
0224         QDir().mkpath(infoPath)))
0225     return false;
0226 
0227   QFile file(infoPath + QLatin1Char('/') + destName + QLatin1String(".trashinfo"));
0228   if (!file.open(QIODevice::WriteOnly))
0229     return false;
0230   QTextStream stream(&file);
0231   stream << QString(QLatin1String("[Trash Info]\nPath=%1\nDeletionDate=%2\n"))
0232     .arg(QString::fromLatin1(QUrl(absPath).toEncoded()),
0233          QDateTime::currentDateTime().toString(Qt::ISODate));
0234   file.close();
0235   return QDir().rename(absPath, filesPath + QLatin1Char('/') + destName);
0236 }
0237 
0238 bool findMountPoint(dev_t dev, QString& mountPoint)
0239 {
0240 #if defined HAVE_MNTENT_H && !defined Q_OS_ANDROID
0241   if (FILE* fp = ::setmntent("/proc/mounts", "r")) {
0242     struct stat st;
0243     struct mntent* mnt;
0244     while ((mnt = ::getmntent(fp)) != nullptr) {
0245       if (::stat(mnt->mnt_dir, &st) != 0) {
0246         continue;
0247       }
0248 
0249       if (st.st_dev == dev) {
0250         ::endmntent(fp);
0251         mountPoint = QString::fromLatin1(mnt->mnt_dir);
0252         return true;
0253       }
0254     }
0255     ::endmntent(fp);
0256   }
0257 #else
0258   Q_UNUSED(dev)
0259   Q_UNUSED(mountPoint)
0260 #endif
0261   return false;
0262 }
0263 
0264 bool findExtVolumeTrash(const QString& volumeRoot, QString& trashDir)
0265 {
0266   struct stat st;
0267   trashDir = volumeRoot + QLatin1String("/.Trash");
0268   uid_t uid = ::getuid();
0269   if (QDir(trashDir).exists() &&
0270       ::lstat(trashDir.toLocal8Bit().data(), &st) == 0 &&
0271       (S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) && (st.st_mode & S_ISVTX))) {
0272     trashDir += QString(QLatin1String("/%1")).arg(uid);
0273   } else {
0274     trashDir += QString(QLatin1String("-%1")).arg(uid);
0275   }
0276   return QDir(trashDir).exists() || QDir().mkpath(trashDir);
0277 }
0278 
0279 } // anonymous namespace
0280 
0281 /**
0282  * Move file or directory to trash.
0283  *
0284  * @param path path to file or directory
0285  *
0286  * @return true if ok.
0287  */
0288 bool CorePlatformTools::moveFileToTrash(const QString& path)
0289 {
0290   QFileInfo fi(path);
0291   const QString absPath(fi.absoluteFilePath());
0292 
0293   if (!fi.exists() || !fi.isWritable())
0294     return false;
0295 
0296   struct stat pathStat;
0297   struct stat trashStat;
0298   if (::lstat(QFile::encodeName(absPath).constData(), &pathStat) != 0 ||
0299       ::lstat(QFile::encodeName(QDir::homePath()).constData(), &trashStat) != 0)
0300     return false;
0301 
0302   QString topDir;
0303   QString trashDir;
0304   if (pathStat.st_dev == trashStat.st_dev) {
0305     QByteArray xdhEnv = qgetenv("XDG_DATA_HOME");
0306     topDir = !xdhEnv.isEmpty() ? QString::fromLatin1(xdhEnv)
0307                                : QDir::homePath() + QLatin1String("/.local/share");
0308     trashDir = topDir + QLatin1String("/Trash");
0309   } else if (!(findMountPoint(pathStat.st_dev, topDir) &&
0310                findExtVolumeTrash(topDir, trashDir))) {
0311     return false;
0312   }
0313   return moveToTrashDir(fi, trashDir);
0314 }
0315 
0316 bool CorePlatformTools::moveToTrash(const QString& path) const
0317 {
0318   return moveFileToTrash(path);
0319 }
0320 
0321 #endif
0322 
0323 /**
0324  * Construct a name filter string suitable for file dialogs.
0325  * @param nameFilters list of description, filter pairs, e.g.
0326  * [("Images", "*.jpg *.jpeg *.png *.webp"), ("All Files", "*")].
0327  * @return name filter string.
0328  */
0329 QString CorePlatformTools::fileDialogNameFilter(
0330     const QList<QPair<QString, QString> >& nameFilters) const
0331 {
0332   return ICorePlatformTools::qtFileDialogNameFilter(nameFilters);
0333 }
0334 
0335 /**
0336  * Get file pattern part of m_nameFilter.
0337  * @param nameFilter name filter string
0338  * @return file patterns, e.g. "*.mp3".
0339  */
0340 QString CorePlatformTools::getNameFilterPatterns(const QString& nameFilter) const
0341 {
0342   return ICorePlatformTools::qtNameFilterPatterns(nameFilter);
0343 }