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

0001 /**
0002  * \file scriptutils.cpp
0003  * QML support functions.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 21 Sep 2014
0008  *
0009  * Copyright (C) 2014-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 "scriptutils.h"
0028 #include <memory>
0029 #include <QMetaProperty>
0030 #include <QCoreApplication>
0031 #include <QFile>
0032 #include <QDir>
0033 #include <QProcess>
0034 #include <QImage>
0035 #include <QBuffer>
0036 #include <QCryptographicHash>
0037 #include <QJSEngine>
0038 #include <QStandardPaths>
0039 #include <QStorageInfo>
0040 #include "pictureframe.h"
0041 #include "saferename.h"
0042 #include "mainwindowconfig.h"
0043 #include "config.h"
0044 
0045 namespace {
0046 
0047 /**
0048  * Create a string list from a NULL terminated array of C strings.
0049  */
0050 QStringList cstringArrayToStringList(const char* const* strs)
0051 {
0052   QStringList result;
0053   while (*strs) {
0054     result.append(QCoreApplication::translate("@default", *strs++));
0055   }
0056   return result;
0057 }
0058 
0059 }
0060 
0061 
0062 ScriptUtils::ScriptUtils(QObject *parent) : QObject(parent)
0063 {
0064 }
0065 
0066 QStringList ScriptUtils::toStringList(const QList<QUrl>& urls)
0067 {
0068   QStringList paths;
0069   paths.reserve(urls.size());
0070   for (const QUrl& url : urls) {
0071     paths.append(url.toLocalFile());
0072   }
0073   return paths;
0074 }
0075 
0076 QList<QPersistentModelIndex> ScriptUtils::toPersistentModelIndexList(const QVariantList& lst)
0077 {
0078   QList<QPersistentModelIndex> indexes;
0079   indexes.reserve(lst.size());
0080   for (const QVariant& var : lst) {
0081     indexes.append(var.toModelIndex());
0082   }
0083   return indexes;
0084 }
0085 
0086 QVariant ScriptUtils::getRoleData(
0087     QObject* modelObj, int row, const QByteArray& roleName,
0088     const QModelIndex& parent)
0089 {
0090   if (auto model = qobject_cast<QAbstractItemModel*>(modelObj)) {
0091     QHash<int,QByteArray> roleHash = model->roleNames();
0092     for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
0093       if (it.value() == roleName) {
0094         return model->index(row, 0, parent).data(it.key());
0095       }
0096     }
0097   }
0098   return QVariant();
0099 }
0100 
0101 bool ScriptUtils::setRoleData(
0102     QObject* modelObj, int row, const QByteArray& roleName,
0103     const QVariant& value, const QModelIndex& parent)
0104 {
0105   if (auto model = qobject_cast<QAbstractItemModel*>(modelObj)) {
0106     QHash<int,QByteArray> roleHash = model->roleNames();
0107     for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
0108       if (it.value() == roleName) {
0109         return model->setData(model->index(row, 0, parent), value, it.key());
0110       }
0111     }
0112   }
0113   return false;
0114 }
0115 
0116 QVariant ScriptUtils::getIndexRoleData(const QModelIndex& index,
0117                                        const QByteArray& roleName)
0118 {
0119   if (const QAbstractItemModel* model = index.model()) {
0120     QHash<int,QByteArray> roleHash = model->roleNames();
0121     for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
0122       if (it.value() == roleName) {
0123         return index.data(it.key());
0124       }
0125     }
0126   }
0127   return QVariant();
0128 }
0129 
0130 QString ScriptUtils::properties(const QObject* obj)
0131 {
0132   QString str;
0133   if (const QMetaObject* meta;
0134       obj && (meta = obj->metaObject()) != nullptr) {
0135     str += QLatin1String("className: ");
0136     str += QString::fromLatin1(meta->className());
0137     for (int i = 0; i < meta->propertyCount(); i++) {
0138       QMetaProperty property = meta->property(i);
0139       const char* name = property.name();
0140       QVariant value = obj->property(name);
0141       str += QLatin1Char('\n');
0142       str += QString::fromLatin1(name);
0143       str += QLatin1String(": ");
0144       str += value.toString();
0145     }
0146   }
0147   return str;
0148 }
0149 
0150 /**
0151  * String list of frame field ID names.
0152  */
0153 QStringList ScriptUtils::getFieldIdNames()
0154 {
0155   return cstringArrayToStringList(Frame::Field::getFieldIdNames());
0156 }
0157 
0158 /**
0159  * String list of text encoding names.
0160  */
0161 QStringList ScriptUtils::getTextEncodingNames()
0162 {
0163   return cstringArrayToStringList(Frame::Field::getTextEncodingNames());
0164 }
0165 
0166 /**
0167  * String list of timestamp format names.
0168  */
0169 QStringList ScriptUtils::getTimestampFormatNames()
0170 {
0171   return cstringArrayToStringList(Frame::Field::getTimestampFormatNames());
0172 }
0173 
0174 /**
0175  * String list of picture type names.
0176  */
0177 QStringList ScriptUtils::getPictureTypeNames()
0178 {
0179   return cstringArrayToStringList(PictureFrame::getPictureTypeNames());
0180 }
0181 
0182 /**
0183  * String list of content type names.
0184  */
0185 QStringList ScriptUtils::getContentTypeNames()
0186 {
0187   return cstringArrayToStringList(Frame::Field::getContentTypeNames());
0188 }
0189 
0190 /**
0191  * Write data to a file.
0192  * @param filePath path to file
0193  * @param data data to write
0194  * @return true if ok.
0195  */
0196 bool ScriptUtils::writeFile(const QString& filePath, const QByteArray& data)
0197 {
0198   bool ok = false;
0199   if (QFile file(filePath);
0200       file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0201     ok = file.write(data) > 0;
0202     file.close();
0203   }
0204   return ok;
0205 }
0206 
0207 /**
0208  * Read data from file
0209  * @param filePath path to file
0210  * @return data read, empty if failed.
0211  */
0212 QByteArray ScriptUtils::readFile(const QString& filePath)
0213 {
0214   QByteArray data;
0215   if (QFile file(filePath); file.open(QIODevice::ReadOnly)) {
0216     data = file.readAll();
0217     file.close();
0218   }
0219   return data;
0220 }
0221 
0222 /**
0223  * Remove file.
0224  * @param filePath path to file
0225  * @return true if ok.
0226  */
0227 bool ScriptUtils::removeFile(const QString& filePath)
0228 {
0229   return QFile::remove(filePath);
0230 }
0231 
0232 /**
0233  * Check if file exists.
0234  * @param filePath path to file
0235  * @return true if file exists.
0236  */
0237 bool ScriptUtils::fileExists(const QString& filePath)
0238 {
0239   return QFile::exists(filePath);
0240 }
0241 
0242 /**
0243  * Check if file is writable.
0244  * @param filePath path to file
0245  * @return true if file is writable.
0246  */
0247 bool ScriptUtils::fileIsWritable(const QString& filePath)
0248 {
0249   return QFileInfo(filePath).isWritable();
0250 }
0251 
0252 /**
0253  * Get permissions of file.
0254  * @param filePath path to file
0255  * @return mode bits of file, e.g. 0x644.
0256  */
0257 int ScriptUtils::getFilePermissions(const QString& filePath)
0258 {
0259   return static_cast<int>(QFile::permissions(filePath));
0260 }
0261 
0262 /**
0263  * Set permissions of file.
0264  * @param filePath path to file
0265  * @param modeBits mode bits of file, e.g. 0x644
0266  * @return true if ok.
0267  */
0268 bool ScriptUtils::setFilePermissions(const QString& filePath, int modeBits)
0269 {
0270   return QFile::setPermissions(filePath, QFile::Permissions(modeBits));
0271 }
0272 
0273 /**
0274  * @brief Get type of file.
0275  * @param filePath path to file
0276  * @return "/" for directories, "@" for symlinks, "*" for executables,
0277  *         " " for files.
0278  */
0279 QString ScriptUtils::classifyFile(const QString& filePath)
0280 {
0281   if (QFileInfo fi(filePath); fi.isSymLink()) {
0282     return QLatin1String("@");
0283   } else {
0284     if (fi.isDir()) {
0285       return QLatin1String("/");
0286     }
0287     if (fi.isExecutable()) {
0288       return QLatin1String("*");
0289     }
0290     if (fi.isFile()) {
0291       return QLatin1String(" ");
0292     }
0293     return QString();
0294   }
0295 }
0296 
0297 /**
0298  * Rename file.
0299  * @param oldName old name
0300  * @param newName new name
0301  * @return true if ok.
0302  */
0303 bool ScriptUtils::renameFile(const QString& oldName, const QString& newName)
0304 {
0305   return Utils::safeRename(oldName, newName);
0306 }
0307 
0308 /**
0309  * Copy file.
0310  * @param source path to source file
0311  * @param dest path to destination file
0312  * @return true if ok.
0313  */
0314 bool ScriptUtils::copyFile(const QString& source, const QString& dest)
0315 {
0316   return QFile::copy(source, dest);
0317 }
0318 
0319 /**
0320  * Create directory.
0321  * @param path path to new directory
0322  * @return true if ok.
0323  */
0324 bool ScriptUtils::makeDir(const QString& path)
0325 {
0326   return QDir().mkpath(path);
0327 }
0328 
0329 /**
0330  * Remove directory.
0331  * @param path path to directory to remove
0332  * @return true if ok.
0333  */
0334 bool ScriptUtils::removeDir(const QString& path)
0335 {
0336   return QDir().rmpath(path);
0337 }
0338 
0339 /**
0340  * Get path of temporary directory.
0341  * @return temporary directory.
0342  */
0343 QString ScriptUtils::tempPath()
0344 {
0345   return QDir::tempPath();
0346 }
0347 
0348 /**
0349  * Get directory containing the user's music.
0350  * @return music directory.
0351  */
0352 QString ScriptUtils::musicPath()
0353 {
0354   return QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
0355 }
0356 
0357 /**
0358  * Get list of currently mounted filesystems.
0359  * @return list with storage information maps containing the keys
0360  * name, displayName, isValid, isReadOnly, isReady, rootPath,
0361  * blockSize, mbytesAvailable, mbytesFree, mbytesTotal.
0362  */
0363 QVariantList ScriptUtils::mountedVolumes()
0364 {
0365   QVariantList result;
0366   for (const QStorageInfo& si : QStorageInfo::mountedVolumes()) {
0367     QVariantMap map;
0368     map.insert(QLatin1String("name"), si.name());
0369     map.insert(QLatin1String("displayName"), si.displayName());
0370     map.insert(QLatin1String("isValid"), si.isValid());
0371     map.insert(QLatin1String("isReadOnly"), si.isReadOnly());
0372     map.insert(QLatin1String("isReady"), si.isReady());
0373     map.insert(QLatin1String("rootPath"), si.rootPath());
0374 #if QT_VERSION >= 0x050600
0375     map.insert(QLatin1String("blockSize"), si.blockSize());
0376 #endif
0377     map.insert(QLatin1String("mbytesAvailable"),
0378                static_cast<int>(si.bytesAvailable() / (1024 * 1024)));
0379     map.insert(QLatin1String("mbytesFree"),
0380                static_cast<int>(si.bytesFree() / (1024 * 1024)));
0381     map.insert(QLatin1String("mbytesTotal"),
0382                static_cast<int>(si.bytesTotal() / (1024 * 1024)));
0383     result.append(map);
0384   }
0385   return result;
0386 }
0387 
0388 /**
0389  * List directory entries.
0390  * @param path directory path
0391  * @param nameFilters list of name filters, e.g. ["*.jpg", "*.png"]
0392  * @param classify if true, add /, @, * for directories, symlinks, executables
0393  * @return list of directory entries.
0394  */
0395 QStringList ScriptUtils::listDir(
0396     const QString& path, const QStringList& nameFilters, bool classify)
0397 {
0398   QStringList dirList;
0399   const QFileInfoList entries = QDir(path).entryInfoList(nameFilters);
0400   dirList.reserve(entries.size());
0401   for (const QFileInfo& fi : entries) {
0402     QString fileName = fi.fileName();
0403     if (classify) {
0404       if (fi.isDir()) fileName += QLatin1Char('/');
0405       else if (fi.isSymLink()) fileName += QLatin1Char('@');
0406       else if (fi.isExecutable()) fileName += QLatin1Char('*');
0407     }
0408     dirList.append(fileName);
0409   }
0410   return dirList;
0411 }
0412 
0413 /**
0414  * Synchronously start a system command.
0415  * @param program executable
0416  * @param args arguments
0417  * @param msecs timeout in milliseconds, -1 for no timeout
0418  * @return [exit code, standard output, standard error], empty list on timeout.
0419  */
0420 QVariantList ScriptUtils::system(
0421     const QString& program, const QStringList& args, int msecs)
0422 {
0423   QProcess proc;
0424   proc.start(program, args);
0425   if (proc.waitForFinished(msecs)) {
0426     return QVariantList()
0427         << proc.exitCode()
0428         << QString::fromLocal8Bit(proc.readAllStandardOutput())
0429         << QString::fromLocal8Bit(proc.readAllStandardError());
0430   }
0431   return QVariantList();
0432 }
0433 
0434 void ScriptUtils::systemAsync(
0435     const QString& program, const QStringList& args, QJSValue callback)
0436 {
0437   auto proc = new QProcess(this);
0438   auto conn = std::make_shared<QMetaObject::Connection>();
0439 #if QT_VERSION >= 0x050d00
0440   *conn = QObject::connect(
0441         proc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
0442           &QProcess::finished),
0443         this, [proc, conn, callback, this](int exitCode, QProcess::ExitStatus) mutable {
0444 #else
0445   *conn = QObject::connect(
0446         proc, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
0447         this, [proc, conn, callback, this](int exitCode) mutable {
0448 #endif
0449     QObject::disconnect(*conn);
0450     if (!callback.isUndefined()) {
0451       QVariantList result{
0452         exitCode,
0453         QString::fromLocal8Bit(proc->readAllStandardOutput()),
0454         QString::fromLocal8Bit(proc->readAllStandardError())
0455       };
0456       callback.call({qjsEngine(this)->toScriptValue(result)});
0457     }
0458   });
0459   proc->start(program, args);
0460 }
0461 
0462 /**
0463  * Get value of environment variable.
0464  * @param varName variable name
0465  * @return value.
0466  */
0467 QByteArray ScriptUtils::getEnv(const QByteArray& varName)
0468 {
0469   return qgetenv(varName.constData());
0470 }
0471 
0472 /**
0473  * Set value of environment variable.
0474  * @param varName variable name
0475  * @param value value to set
0476  * @return true if value could be set.
0477  */
0478 bool ScriptUtils::setEnv(const QByteArray& varName, const QByteArray& value)
0479 {
0480   return qputenv(varName, value);
0481 }
0482 
0483 /**
0484  * Get version of Kid3.
0485  * @return Kid3 version string, e.g. "3.3.0".
0486  */
0487 QString ScriptUtils::getKid3Version()
0488 {
0489   return QLatin1String(VERSION);
0490 }
0491 
0492 /**
0493  * Get release year of Kid3.
0494  * @return Kid3 year string, e.g. "2015".
0495  */
0496 QString ScriptUtils::getKid3ReleaseYear()
0497 {
0498   return QLatin1String(RELEASE_YEAR);
0499 }
0500 
0501 /**
0502  * Get version of Qt.
0503  * @return Qt version string, e.g. "5.4.1".
0504  */
0505 QString ScriptUtils::getQtVersion()
0506 {
0507   return QString::fromLatin1(qVersion());
0508 }
0509 
0510 /**
0511  * Get hex string of the MD5 hash of data.
0512  * This is a replacement for Qt::md5(), which does only work with strings.
0513  * @param data data bytes
0514  * @return MD5 sum.
0515  */
0516 QString ScriptUtils::getDataMd5(const QByteArray& data)
0517 {
0518   QByteArray result = QCryptographicHash::hash(data, QCryptographicHash::Md5);
0519   return QLatin1String(result.toHex());
0520 }
0521 
0522 /**
0523  * Get size of byte array.
0524  * @param data data bytes
0525  * @return number of bytes in @a data.
0526  */
0527 int ScriptUtils::getDataSize(const QByteArray& data)
0528 {
0529   return data.size();
0530 }
0531 
0532 /**
0533  * Create an image from data bytes.
0534  * @param data data bytes
0535  * @param format image format, default is "JPG"
0536  * @return image variant.
0537  */
0538 QVariant ScriptUtils::dataToImage(const QByteArray& data,
0539                                   const QByteArray& format)
0540 {
0541   QImage img(QImage::fromData(data, format.constData()));
0542   return QVariant::fromValue(img);
0543 }
0544 
0545 /**
0546  * Get data bytes from image.
0547  * @param var image variant
0548  * @param format image format, default is "JPG"
0549  * @return data bytes.
0550  */
0551 QByteArray ScriptUtils::dataFromImage(const QVariant& var,
0552                                       const QByteArray& format)
0553 {
0554   QByteArray data;
0555   if (auto img(var.value<QImage>()); !img.isNull()) {
0556     QBuffer buffer(&data);
0557     buffer.open(QIODevice::WriteOnly);
0558     img.save(&buffer, format.constData());
0559   }
0560   return data;
0561 }
0562 
0563 /**
0564  * Load an image from a file.
0565  * @param filePath path to file
0566  * @return image variant.
0567  */
0568 QVariant ScriptUtils::loadImage(const QString& filePath)
0569 {
0570   QImage img(filePath);
0571   return QVariant::fromValue(img);
0572 }
0573 
0574 /**
0575  * Save an image to a file.
0576  * @param var image variant
0577  * @param filePath path to file
0578  * @param format image format, default is "JPG"
0579  * @return true if ok.
0580  */
0581 bool ScriptUtils::saveImage(const QVariant& var, const QString& filePath,
0582                             const QByteArray& format)
0583 {
0584   if (auto img(var.value<QImage>()); !img.isNull()) {
0585     return img.save(filePath, format.constData());
0586   }
0587   return false;
0588 }
0589 
0590 /**
0591  * Get properties of an image.
0592  * @param var image variant
0593  * @return map containing "width", "height", "depth" and "colorCount",
0594  * empty if invalid image.
0595  */
0596 QVariantMap ScriptUtils::imageProperties(const QVariant& var)
0597 {
0598   QVariantMap map;
0599   if (auto img(var.value<QImage>()); !img.isNull()) {
0600     map.insert(QLatin1String("width"), img.width());
0601     map.insert(QLatin1String("height"), img.height());
0602     map.insert(QLatin1String("depth"), img.depth());
0603     map.insert(QLatin1String("colorCount"), img.colorCount());
0604   }
0605   return map;
0606 }
0607 
0608 /**
0609  * Scale an image.
0610  * @param var image variant
0611  * @param width scaled width, -1 to keep aspect ratio
0612  * @param height scaled height, -1 to keep aspect ratio
0613  * @return scaled image variant.
0614  */
0615 QVariant ScriptUtils::scaleImage(const QVariant& var, int width, int height)
0616 {
0617   if (auto img(var.value<QImage>()); !img.isNull()) {
0618     if (width > 0 && height > 0) {
0619       return QVariant::fromValue(img.scaled(width, height,
0620                             Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
0621     }
0622     if (width > 0) {
0623       return QVariant::fromValue(img.scaledToWidth(width,
0624                                                    Qt::SmoothTransformation));
0625     }
0626     if (height > 0) {
0627       return QVariant::fromValue(img.scaledToHeight(height,
0628                                                     Qt::SmoothTransformation));
0629     }
0630   }
0631   return QVariant();
0632 }