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 }