/* This file is part of Clementine. Copyright 2010, David Sansome Clementine 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 3 of the License, or (at your option) any later version. Clementine 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 Clementine. If not, see . */ #include "config.h" #include "languageengine.h" #include "script.h" #include "scriptinterface.h" #include "scriptmanager.h" #include "uiinterface.h" #include "core/utilities.h" #ifdef HAVE_SCRIPTING_PYTHON # include "scripting/python/pythonengine.h" #endif #ifdef Q_OS_MAC # include "core/mac_startup.h" #endif #include #include #include #include #include #include #include const char* ScriptManager::kSettingsGroup = "Scripts"; ScriptManager::ScriptManager(QObject* parent) : QAbstractListModel(parent), ui_interface_(new UIInterface(this)), watcher_(new QFileSystemWatcher(this)), rescan_timer_(new QTimer(this)) { #ifdef HAVE_SCRIPTING_PYTHON engines_ << new PythonEngine(this); #endif connect(watcher_, SIGNAL(directoryChanged(QString)), SLOT(ScriptDirectoryChanged())); rescan_timer_->setSingleShot(true); rescan_timer_->setInterval(1000); connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanScripts())); // Create the user's scripts directory if it doesn't exist yet QString local_path = Utilities::GetConfigPath(Utilities::Path_Scripts); if (!QFile::exists(local_path)) { if (!QDir().mkpath(local_path)) { qWarning() << "Couldn't create directory" << local_path; } } search_paths_ << local_path #ifdef USE_INSTALL_PREFIX << CMAKE_INSTALL_PREFIX "/share/clementine/scripts" #endif << "/usr/share/clementine/scripts" << "/usr/local/share/clementine/scripts"; #if defined(Q_OS_WIN32) search_paths_ << QCoreApplication::applicationDirPath() + "/scripts"; #elif defined(Q_OS_MAC) search_paths_ << mac::GetResourcesPath() + "/scripts"; #endif } ScriptManager::~ScriptManager() { foreach (const ScriptInfo& info, info_) { if (info.loaded()) { info.loaded()->language()->DestroyScript(info.loaded()); } } qDeleteAll(engines_); } void ScriptManager::Init(const GlobalData& data) { data_ = data; // Load settings LoadSettings(); // Search for scripts info_ = LoadAllScriptInfo().values(); // Enable the ones that were enabled last time for (int i=0 ; iid())) { // Find an engine for it LanguageEngine* engine = EngineForLanguage(info->language()); if (!engine) { qWarning() << "Unknown language in" << info->path(); return; } info->set_loaded(engine->CreateScript(*info)); if (!info->loaded()) { // Failed to load? Disable it so we don't try again enabled_scripts_.remove(info->id()); SaveSettings(); } } } QMap ScriptManager::LoadAllScriptInfo() const { QMap ret; foreach (const QString& search_path, search_paths_) { if (!QFile::exists(search_path)) continue; if (!watcher_->directories().contains(search_path)) { watcher_->addPath(search_path); } QDirIterator it(search_path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Executable, QDirIterator::FollowSymlinks); while (it.hasNext()) { it.next(); const QString path = it.filePath(); if (!watcher_->directories().contains(path)) { watcher_->addPath(path); } ScriptInfo info; info.InitFromDirectory(this, path); if (!info.is_valid()) { qWarning() << "Not a valid Clementine script directory, ignoring:" << path; continue; } if (ret.contains(info.id())) { // Seen this script already continue; } ret.insert(info.id(), info); } } return ret; } void ScriptManager::LoadSettings() { QSettings s; s.beginGroup(kSettingsGroup); enabled_scripts_ = QSet::fromList( s.value("enabled_scripts").toStringList()); } void ScriptManager::SaveSettings() const { QSettings s; s.beginGroup(kSettingsGroup); s.setValue("enabled_scripts", QVariant::fromValue(enabled_scripts_.toList())); } LanguageEngine* ScriptManager::EngineForLanguage(const QString& language_name) const { foreach (LanguageEngine* engine, engines_) { if (engine->name() == language_name) { return engine; } } return NULL; } LanguageEngine* ScriptManager::EngineForLanguage(ScriptInfo::Language language) const { foreach (LanguageEngine* engine, engines_) { if (engine->language() == language) { return engine; } } return NULL; } int ScriptManager::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return info_.count(); } QVariant ScriptManager::data(const QModelIndex& index, int role) const { if (index.parent().isValid() || index.row() < 0 || index.row() >= info_.count()) return QVariant(); const ScriptInfo& info = info_[index.row()]; switch (role) { case Qt::DisplayRole: return info.name(); case Qt::DecorationRole: return info.icon(); case Role_Author: return info.author(); case Role_Description: return info.description(); case Role_Language: return info.language(); case Role_ScriptFile: return info.script_file(); case Role_Url: return info.url(); case Role_IsEnabled: return info.loaded() != NULL; default: return QVariant(); } } void ScriptManager::Enable(const QModelIndex& index) { if (index.row() < 0 || index.row() >= info_.count()) return; ScriptInfo* info = &info_[index.row()]; if (info->loaded()) return; // Find an engine for it LanguageEngine* engine = EngineForLanguage(info->language()); if (!engine) { qWarning() << "Unknown language in" << info->path(); return; } // Load the script info->set_loaded(engine->CreateScript(*info)); // If it loaded correctly then automatically load it in the future if (info->loaded()) { enabled_scripts_.insert(info->id()); SaveSettings(); } emit dataChanged(index, index); } void ScriptManager::Disable(const QModelIndex& index) { if (index.row() < 0 || index.row() >= info_.count()) return; ScriptInfo* info = &info_[index.row()]; if (!info->loaded()) return; info->loaded()->language()->DestroyScript(info->loaded()); info->set_loaded(NULL); enabled_scripts_.remove(info->id()); SaveSettings(); emit dataChanged(index, index); } void ScriptManager::ShowSettingsDialog(const QModelIndex& index) { if (index.row() < 0 || index.row() >= info_.count()) return; ScriptInfo* info = &info_[index.row()]; if (!info->loaded()) return; info->loaded()->interface()->ShowSettingsDialog(); } void ScriptManager::AddLogLine(const QString& who, const QString& message, bool error) { foreach (const QString& line, message.split("\n", QString::SkipEmptyParts)) { QString plain = "[" + who + "] " + line; QString html = "[" + Qt::escape(who) + "] " + Qt::escape(line); html.replace(' ', " "); if (error) { html = "" + html + ""; } log_lines_ << html; log_lines_plain_ << plain; emit LogLineAdded(html); qDebug() << plain.toLocal8Bit().constData(); } } void ScriptManager::ScriptDirectoryChanged() { rescan_timer_->start(); } void ScriptManager::RescanScripts() { // Get the new list of scripts QMap new_info = LoadAllScriptInfo(); // Look at existing scripts, find ones that have changed or been deleted for (int i=0 ; iid(); if (!new_info.contains(id)) { // This script was deleted - unload it and remove it from the model Disable(index(i)); beginRemoveRows(QModelIndex(), i, i); info_.removeAt(i); endRemoveRows(); --i; continue; } if (*info != new_info[id]) { // This script was modified - change the metadata in the existing entry info->TakeMetadataFrom(new_info[id]); emit dataChanged(index(i), index(i)); AddLogLine("Watcher", tr("The '%1' script was modified, you might have to reload it").arg(id), false); } new_info.remove(id); } // Things that are left in new_info are newly added scripts if (!new_info.isEmpty()) { const int begin = info_.count(); const int end = begin + new_info.count() - 1; beginInsertRows(QModelIndex(), begin, end); info_.append(new_info.values()); for (int i=begin ; i<=end ; ++i) { MaybeAutoEnable(&info_[i]); } endInsertRows(); } }