diff --git a/renamingutility/filesystemitem.cpp b/renamingutility/filesystemitem.cpp index 821a48e..e78474f 100644 --- a/renamingutility/filesystemitem.cpp +++ b/renamingutility/filesystemitem.cpp @@ -15,8 +15,8 @@ const QString &emptyStr() { return emptyStr; } -FileSystemItem::FileSystemItem(ItemStatus status, ItemType type, const QString &name) : - m_parent(nullptr), +FileSystemItem::FileSystemItem(ItemStatus status, ItemType type, const QString &name, FileSystemItem *parent) : + m_parent(parent), m_counterpart(nullptr), m_status(status), m_type(type), @@ -25,7 +25,11 @@ FileSystemItem::FileSystemItem(ItemStatus status, ItemType type, const QString & m_name(name), m_checked(false), m_checkable(false) -{} +{ + if(m_parent) { + m_parent->m_children << this; + } +} FileSystemItem::~FileSystemItem() { diff --git a/renamingutility/filesystemitem.h b/renamingutility/filesystemitem.h index f5df45f..97061b5 100644 --- a/renamingutility/filesystemitem.h +++ b/renamingutility/filesystemitem.h @@ -24,7 +24,7 @@ enum class ItemType { class FileSystemItem { public: - FileSystemItem(ItemStatus status = ItemStatus::Current, ItemType type = ItemType::File, const QString &name = QString()); + FileSystemItem(ItemStatus status = ItemStatus::Current, ItemType type = ItemType::File, const QString &name = QString(), FileSystemItem *parent = nullptr); ~FileSystemItem(); FileSystemItem(const FileSystemItem &other) = delete; FileSystemItem &operator =(const FileSystemItem &other) = delete; diff --git a/renamingutility/renamingengine.cpp b/renamingutility/renamingengine.cpp index 9e25130..d4dc41a 100644 --- a/renamingutility/renamingengine.cpp +++ b/renamingutility/renamingengine.cpp @@ -4,6 +4,8 @@ #include "./filteredfilesystemitemmodel.h" #include "./scriptfunctions.h" +#include + #include #include #include @@ -21,6 +23,7 @@ namespace RenamingUtility { RemamingEngine::RemamingEngine(QObject *parent) : QObject(parent), + m_go(m_engine.globalObject()), m_itemsProcessed(0), m_errorsOccured(0), m_aborted(false), @@ -41,26 +44,24 @@ bool RemamingEngine::generatePreview(const QScriptProgram &scriptProgram, const if(!m_mutex.try_lock()) { return false; } - setRootItem(nullptr); + lock_guard guard(m_mutex, adopt_lock); + setRootItem(); m_program = scriptProgram; m_includeSubdirs = includeSubdirs; m_dir = rootDirectory; auto startFunc = [this] () { { lock_guard guard(m_mutex); - { - lock_guard guard(m_abortedMutex); - m_aborted = false; - } + m_aborted.store(false); m_itemsProcessed = 0; m_errorsOccured = 0; - m_newlyGeneratedRootItem.reset(generatePreview(m_dir)); + m_go.setProperty("persistent", m_persistent = m_engine.newObject(), QScriptValue::Undeletable); + m_newlyGeneratedRootItem = generatePreview(m_dir); } emit previewGenerated(); }; std::thread thread(startFunc); thread.detach(); - m_mutex.unlock(); return true; } @@ -72,13 +73,11 @@ bool RemamingEngine::applyChangings() if(!m_mutex.try_lock()) { return false; } + lock_guard guard(m_mutex, adopt_lock); auto startFunc = [this] () { { lock_guard guard(m_mutex); - { - lock_guard guard(m_abortedMutex); - m_aborted = false; - } + m_aborted.store(false); m_itemsProcessed = 0; m_errorsOccured = 0; applyChangings(m_rootItem.get()); @@ -87,7 +86,6 @@ bool RemamingEngine::applyChangings() }; std::thread thread(startFunc); thread.detach(); - m_mutex.unlock(); return true; } @@ -103,22 +101,20 @@ bool RemamingEngine::isBusy() void RemamingEngine::abort() { - lock_guard guard(m_abortedMutex); - m_aborted = true; + m_aborted.store(true); } bool RemamingEngine::isAborted() { - lock_guard guard(m_abortedMutex); - return m_aborted; + return m_aborted.load(); } bool RemamingEngine::clearPreview() { if(m_mutex.try_lock()) { + lock_guard guard(m_mutex, adopt_lock); updateModel(nullptr); m_rootItem.reset(); - m_mutex.unlock(); return true; } else { return false; @@ -153,7 +149,7 @@ FilteredFileSystemItemModel *RemamingEngine::previewModel() void RemamingEngine::processPreviewGenerated() { - setRootItem(m_newlyGeneratedRootItem.release()); + setRootItem(move(m_newlyGeneratedRootItem)); } void RemamingEngine::processChangingsApplied() @@ -162,10 +158,10 @@ void RemamingEngine::processChangingsApplied() updateModel(m_rootItem.get()); } -inline void RemamingEngine::setRootItem(FileSystemItem *rootItem) +inline void RemamingEngine::setRootItem(unique_ptr &&rootItem) { - updateModel(rootItem); - m_rootItem.reset(rootItem); + updateModel(rootItem.get()); + m_rootItem = move(rootItem); } void RemamingEngine::updateModel(FileSystemItem *rootItem) @@ -175,10 +171,9 @@ void RemamingEngine::updateModel(FileSystemItem *rootItem) } } -FileSystemItem *RemamingEngine::generatePreview(const QDir &dir, FileSystemItem *parent) +unique_ptr RemamingEngine::generatePreview(const QDir &dir, FileSystemItem *parent) { - FileSystemItem *item = new FileSystemItem(ItemStatus::Current, ItemType::Dir, dir.dirName()); - item->setParent(parent); + auto item = make_unique(ItemStatus::Current, ItemType::Dir, dir.dirName(), parent); item->setApplied(false); QFileInfoList entries = dir.entryInfoList(); foreach(const QFileInfo &entry, entries) { @@ -186,12 +181,11 @@ FileSystemItem *RemamingEngine::generatePreview(const QDir &dir, FileSystemItem || entry.fileName() == QLatin1String(".")) { continue; } - FileSystemItem *subItem; + FileSystemItem *subItem; // will be deleted by parent if(entry.isDir() && m_includeSubdirs) { - subItem = generatePreview(QDir(entry.absoluteFilePath()), item); + subItem = generatePreview(QDir(entry.absoluteFilePath()), item.get()).release(); } else if(entry.isFile()) { - subItem = new FileSystemItem(ItemStatus::Current, ItemType::File, entry.fileName()); - subItem->setParent(item); + subItem = new FileSystemItem(ItemStatus::Current, ItemType::File, entry.fileName(), item.get()); subItem->setApplied(false); } else { subItem = nullptr; @@ -295,20 +289,21 @@ void RemamingEngine::setError(const QList items) void RemamingEngine::executeScriptForItem(const QFileInfo &fileInfo, FileSystemItem *item) { - QScriptEngine engine; - setupGlobalObject(engine, fileInfo, item); - QScriptValue res = engine.evaluate(m_program); - - if(engine.hasUncaughtException()) { + // execute script + setupGlobalObject(fileInfo, item); + QScriptValue res = m_engine.evaluate(m_program); + if(m_engine.hasUncaughtException()) { + // handle error item->setErrorOccured(true); item->setNote(res.toString()); + m_engine.clearExceptions(); } else { - QScriptValue go = engine.globalObject(); - QScriptValue newName = go.property("newName"); - QScriptValue newRelativeDirectory = go.property("newRelativeDirectory"); + // create preview for action + QScriptValue newName = m_go.property("newName"); + QScriptValue newRelativeDirectory = m_go.property("newRelativeDirectory"); ActionType action = ActionType::Skip; - if(engine.globalObject().property("action").isNumber()) { - action = static_cast(engine.globalObject().property("action").toInt32()); + if(m_go.property("action").isNumber()) { + action = static_cast(m_go.property("action").toInt32()); } switch(action) { case ActionType::Rename: @@ -325,8 +320,7 @@ void RemamingEngine::executeScriptForItem(const QFileInfo &fileInfo, FileSystemI item->setNote(tr("name is already used at new location")); item->setErrorOccured(true); } else { - FileSystemItem *counterpart = new FileSystemItem(ItemStatus::New, item->type(), counterpartName); - counterpart->setParent(counterpartParent); + auto *counterpart = new FileSystemItem(ItemStatus::New, item->type(), counterpartName, counterpartParent); item->setCounterpart(counterpart); counterpart->setCheckable(true); counterpart->setChecked(true); @@ -359,24 +353,29 @@ void RemamingEngine::executeScriptForItem(const QFileInfo &fileInfo, FileSystemI } } -void RemamingEngine::setupGlobalObject(QScriptEngine &engine, const QFileInfo &file, FileSystemItem *item) +void RemamingEngine::setupGlobalObject(const QFileInfo &file, FileSystemItem *item) { - QScriptValue go = engine.globalObject(); - go.setProperty("currentPath", file.absoluteFilePath(), QScriptValue::ReadOnly); - go.setProperty("currentName", item->name(), QScriptValue::ReadOnly); - go.setProperty("currentRelativeDirectory", item->relativeDir(), QScriptValue::ReadOnly); - go.setProperty("isDir", item->type() == ItemType::Dir, QScriptValue::ReadOnly); - go.setProperty("isFile", item->type() == ItemType::File, QScriptValue::ReadOnly); - go.setProperty("action", QScriptValue(static_cast(ActionType::Rename)), QScriptValue::Undeletable); - go.setProperty("parseFileInfo", engine.newFunction(ScriptFunctions::parseFileInfo), QScriptValue::ReadOnly); - go.setProperty("parseFileName", engine.newFunction(ScriptFunctions::parseFileName), QScriptValue::ReadOnly); - go.setProperty("allFiles", engine.newFunction(ScriptFunctions::allFiles), QScriptValue::ReadOnly); - go.setProperty("firstFile", engine.newFunction(ScriptFunctions::firstFile), QScriptValue::ReadOnly); - go.setProperty("writeLog", engine.newFunction(ScriptFunctions::writeLog), QScriptValue::ReadOnly); - QScriptValue actionObject = engine.newObject(); + // create new global object to clean previous variables ... + m_go = m_engine.newObject(); + // ... except the persistent object + m_go.setProperty("persistent", m_persistent, QScriptValue::Undeletable); + // provide properties/functions + m_go.setProperty("currentPath", file.absoluteFilePath(), QScriptValue::ReadOnly); + m_go.setProperty("currentName", item->name(), QScriptValue::ReadOnly); + m_go.setProperty("currentRelativeDirectory", item->relativeDir(), QScriptValue::ReadOnly); + m_go.setProperty("isDir", item->type() == ItemType::Dir, QScriptValue::ReadOnly); + m_go.setProperty("isFile", item->type() == ItemType::File, QScriptValue::ReadOnly); + m_go.setProperty("action", QScriptValue(static_cast(ActionType::Rename)), QScriptValue::Undeletable); + m_go.setProperty("parseFileInfo", m_engine.newFunction(ScriptFunctions::parseFileInfo), QScriptValue::ReadOnly); + m_go.setProperty("parseFileName", m_engine.newFunction(ScriptFunctions::parseFileName), QScriptValue::ReadOnly); + m_go.setProperty("allFiles", m_engine.newFunction(ScriptFunctions::allFiles), QScriptValue::ReadOnly); + m_go.setProperty("firstFile", m_engine.newFunction(ScriptFunctions::firstFile), QScriptValue::ReadOnly); + m_go.setProperty("writeLog", m_engine.newFunction(ScriptFunctions::writeLog), QScriptValue::ReadOnly); + QScriptValue actionObject = m_engine.newObject(); actionObject.setProperty("rename", QScriptValue(static_cast(ActionType::Rename)), QScriptValue::ReadOnly); actionObject.setProperty("skip", QScriptValue(static_cast(ActionType::Skip)), QScriptValue::ReadOnly); - go.setProperty("actionType", actionObject, QScriptValue::ReadOnly); + m_go.setProperty("actionType", actionObject, QScriptValue::ReadOnly); + m_engine.setGlobalObject(m_go); } } // namespace RenamingUtility diff --git a/renamingutility/renamingengine.h b/renamingutility/renamingengine.h index 5f2dbcb..606270d 100644 --- a/renamingutility/renamingengine.h +++ b/renamingutility/renamingengine.h @@ -5,17 +5,20 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE class QFileInfo; class QScriptProgram; -class QScriptValue; class QScriptContext; -class QScriptEngine; +//class QScriptEngine; +//class QScriptValue; QT_END_NAMESPACE #include #include +#include namespace RenamingUtility { @@ -57,20 +60,22 @@ private slots: void processChangingsApplied(); private: - void setRootItem(FileSystemItem *rootItem); + void setRootItem(std::unique_ptr &&rootItem = std::unique_ptr()); void updateModel(FileSystemItem *rootItem); - FileSystemItem *generatePreview(const QDir &dir, FileSystemItem *parent = nullptr); + std::unique_ptr generatePreview(const QDir &dir, FileSystemItem *parent = nullptr); void applyChangings(FileSystemItem *parentItem); static void setError(const QList items); void executeScriptForItem(const QFileInfo &fileInfo, FileSystemItem *item); - static void setupGlobalObject(QScriptEngine &engine, const QFileInfo &file, FileSystemItem *item); + void setupGlobalObject(const QFileInfo &file, FileSystemItem *item); + QScriptEngine m_engine; + QScriptValue m_go; + QScriptValue m_persistent; std::unique_ptr m_rootItem; std::unique_ptr m_newlyGeneratedRootItem; int m_itemsProcessed; int m_errorsOccured; - bool m_aborted; - std::mutex m_abortedMutex; + std::atomic m_aborted; QScriptProgram m_program; QDir m_dir; bool m_includeSubdirs; diff --git a/renamingutility/scriptfunctions.cpp b/renamingutility/scriptfunctions.cpp index 8bff8ec..b44105f 100644 --- a/renamingutility/scriptfunctions.cpp +++ b/renamingutility/scriptfunctions.cpp @@ -28,33 +28,67 @@ namespace RenamingUtility { Necessary for lupdate. */ +QScriptValue &operator <<(QScriptValue ¬ificationsObject, const StatusProvider &statusProvider) +{ + quint32 counter = 0; + for(const auto ¬ification : statusProvider.notifications()) { + QScriptValue val; + val.setProperty("msg", QString::fromLocal8Bit(notification.message().data()), QScriptValue::ReadOnly); + val.setProperty("critical", notification.type() == NotificationType::Critical, QScriptValue::ReadOnly); + notificationsObject.setProperty(counter, val); + ++counter; + } + return notificationsObject; +} + +QScriptValue &operator <<(QScriptValue &tagObject, const Tag &tag) +{ + // text fields + tagObject.setProperty("title", tagValueToQString(tag.value(KnownField::Title)), QScriptValue::ReadOnly); + tagObject.setProperty("artist", tagValueToQString(tag.value(KnownField::Artist)), QScriptValue::ReadOnly); + tagObject.setProperty("album", tagValueToQString(tag.value(KnownField::Album)), QScriptValue::ReadOnly); + tagObject.setProperty("year", tagValueToQString(tag.value(KnownField::Year)), QScriptValue::ReadOnly); + tagObject.setProperty("comment", tagValueToQString(tag.value(KnownField::Comment)), QScriptValue::ReadOnly); + tagObject.setProperty("genre", tagValueToQString(tag.value(KnownField::Genre)), QScriptValue::ReadOnly); + tagObject.setProperty("encoder", tagValueToQString(tag.value(KnownField::Encoder)), QScriptValue::ReadOnly); + tagObject.setProperty("language", tagValueToQString(tag.value(KnownField::Language)), QScriptValue::ReadOnly); + tagObject.setProperty("descriptions", tagValueToQString(tag.value(KnownField::Description)), QScriptValue::ReadOnly); + // numeric fields + try { + tagObject.setProperty("partNumber", tag.value(KnownField::PartNumber).toInteger(), QScriptValue::ReadOnly); + } catch(ConversionException &) {} + try { + tagObject.setProperty("totalParts", tag.value(KnownField::TotalParts).toInteger(), QScriptValue::ReadOnly); + } catch(ConversionException &) {} + PositionInSet pos; + try { + pos = tag.value(KnownField::TrackPosition).toPositionIntSet(); + } catch(ConversionException &) {} + tagObject.setProperty("trackPos", pos.position(), QScriptValue::ReadOnly); + tagObject.setProperty("trackTotal", pos.total(), QScriptValue::ReadOnly); + pos = PositionInSet(); + try { + pos = tag.value(KnownField::DiskPosition).toPositionIntSet(); + } catch(ConversionException &) {} + tagObject.setProperty("diskPos", pos.position(), QScriptValue::ReadOnly); + tagObject.setProperty("diskTotal", pos.total(), QScriptValue::ReadOnly); + // notifications + tagObject.setProperty("hasCriticalNotifications", tag.hasCriticalNotifications(), QScriptValue::ReadOnly); + return tagObject; +} + QScriptValue ScriptFunctions::parseFileInfo(QScriptContext *context, QScriptEngine *engine) { if(context->argumentCount() != 1 && !context->argument(0).isString()) { return QScriptValue(); } - QString fileName = context->argument(0).toString(); - - auto addNotifications = [] (QScriptValue ¬ificationObject, const StatusProvider &statusProvider) { - quint32 counter = 0; - for(const Notification ¬ification : statusProvider.notifications()) { - QScriptValue val; - val.setProperty("msg", QString::fromLocal8Bit(notification.message().c_str()), QScriptValue::ReadOnly); - val.setProperty("critical", notification.type() == NotificationType::Critical, QScriptValue::ReadOnly); - notificationObject.setProperty(counter, val); - ++counter; - } - }; - - QScriptValue fileInfoObject = engine->newObject(); - QScriptValue tagObject = engine->newObject(); - QScriptValue tagNotificationObject = engine->newArray(); - + auto fileName = context->argument(0).toString(); MediaFileInfo fileInfo(fileName.toLocal8Bit().data()); - fileInfoObject.setProperty("currentName", QString::fromLocal8Bit(fileInfo.fileName(false).c_str())); - fileInfoObject.setProperty("currentBaseName", QString::fromLocal8Bit(fileInfo.fileName(true).c_str())); - QString suffix = QString::fromLocal8Bit(fileInfo.extension().c_str()); + QScriptValue fileInfoObject = engine->newObject(); + fileInfoObject.setProperty("currentName", QString::fromLocal8Bit(fileInfo.fileName(false).data())); + fileInfoObject.setProperty("currentBaseName", QString::fromLocal8Bit(fileInfo.fileName(true).data())); + QString suffix = QString::fromLocal8Bit(fileInfo.extension().data()); if(suffix.startsWith('.')) { suffix.remove(0, 1); } @@ -70,7 +104,7 @@ QScriptValue ScriptFunctions::parseFileInfo(QScriptContext *context, QScriptEngi } QScriptValue mainNotificationObject = engine->newArray(fileInfo.notifications().size()); - addNotifications(mainNotificationObject, fileInfo); + mainNotificationObject << fileInfo; critical |= fileInfo.hasCriticalNotifications(); fileInfoObject.setProperty("hasCriticalNotifications", critical); fileInfoObject.setProperty("notifications", mainNotificationObject); @@ -80,32 +114,25 @@ QScriptValue ScriptFunctions::parseFileInfo(QScriptContext *context, QScriptEngi vector tags; fileInfo.tags(tags); - for(vector::const_iterator tagIterator = tags.cbegin(), end = tags.cend(); - tagIterator != end; ++tagIterator) { - const Tag &tag = **tagIterator; - tagObject.setProperty("title", tagValueToQString(tag.value(KnownField::Title)), QScriptValue::ReadOnly); - tagObject.setProperty("artist", tagValueToQString(tag.value(KnownField::Artist)), QScriptValue::ReadOnly); - tagObject.setProperty("album", tagValueToQString(tag.value(KnownField::Album)), QScriptValue::ReadOnly); - tagObject.setProperty("year", tagValueToQString(tag.value(KnownField::Year)), QScriptValue::ReadOnly); - tagObject.setProperty("comment", tagValueToQString(tag.value(KnownField::Comment)), QScriptValue::ReadOnly); - PositionInSet pos; - try { - pos = tag.value(KnownField::TrackPosition).toPositionIntSet(); - } catch(ConversionException &) {} - tagObject.setProperty("trackPos", pos.position(), QScriptValue::ReadOnly); - tagObject.setProperty("trackTotal", pos.total(), QScriptValue::ReadOnly); - pos = PositionInSet(); - try { - pos = tag.value(KnownField::DiskPosition).toPositionIntSet(); - } catch(ConversionException &) {} - tagObject.setProperty("diskPos", pos.position(), QScriptValue::ReadOnly); - tagObject.setProperty("diskTotal", pos.total(), QScriptValue::ReadOnly); + QScriptValue combinedTagObject = engine->newObject(); + QScriptValue combinedTagNotifications = engine->newArray(); + QScriptValue tagsObject = engine->newArray(tags.size()); + uint32 tagIndex = 0; - addNotifications(tagNotificationObject, tag); - tagObject.setProperty("hasCriticalNotifications", tag.hasCriticalNotifications(), QScriptValue::ReadOnly); + for(auto tagIterator = tags.cbegin(), end = tags.cend(); tagIterator != end; ++tagIterator, ++tagIndex) { + const Tag &tag = **tagIterator; + QScriptValue tagObject = engine->newObject(); + combinedTagObject << tag; + combinedTagNotifications << tag; + tagObject << tag; + QScriptValue tagNotificationsObject = engine->newArray(tag.notifications().size()); + tagNotificationsObject << tag; + tagObject.setProperty("notifications", tagNotificationsObject, QScriptValue::ReadOnly); + tagsObject.setProperty(tagIndex, tagObject, QScriptValue::ReadOnly); } - fileInfoObject.setProperty("notifications", tagNotificationObject, QScriptValue::ReadOnly); - fileInfoObject.setProperty("tag", tagObject, QScriptValue::ReadOnly); + combinedTagObject.setProperty("notifications", combinedTagNotifications, QScriptValue::ReadOnly); + fileInfoObject.setProperty("tag", combinedTagObject, QScriptValue::ReadOnly); + fileInfoObject.setProperty("tags", tagsObject, QScriptValue::ReadOnly); return fileInfoObject; } diff --git a/resources/scripts/renamefiles/example1.js b/resources/scripts/renamefiles/example1.js index 72d9499..9e8547b 100644 --- a/resources/scripts/renamefiles/example1.js +++ b/resources/scripts/renamefiles/example1.js @@ -58,7 +58,7 @@ function appropriateDigitCount(pos, total) { */ function validFileName(name) { if(name !== undefined) { - return name.replace(/[\/\\<>?!*|:\"\n\f\r]/gi, ""); + return name.replace(/[\/\\]/gi, " - ").replace(/[<>?!*|:\"\n\f\r]/gi, ""); } else { return ""; } @@ -70,7 +70,7 @@ function validFileName(name) { */ function validDirectoryName(name) { if(name !== undefined) { - return name.replace(/[\/\\<>?!*|:\".\n\f\r]/gi, ""); + return name.replace(/[\/\\]/gi, " - ").replace(/[<>?!*|:\".\n\f\r]/gi, ""); } else { return ""; }