35 #include <QDirIterator>
36 #include <QDomDocument>
37 #include <QDomElement>
59 QStringList dirs = {path};
60 QDirIterator iter {path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
61 while (iter.hasNext())
64 for (
const QString &dir :
asConst(dirs))
67 if (dir.endsWith(
"/__pycache__"))
74 const QStringList
files = QDir(dir).entryList(QDir::Files);
77 if (
file.endsWith(
".pyc"))
87 : m_updateUrl(QLatin1String(
"http://searchplugins.qbittorrent.org/nova3/engines/"))
124 plugins << plugin->name;
137 for (
const QString &cat : plugin->supportedCategories)
139 if (!result.contains(cat))
151 if (pluginName ==
"all")
153 else if ((pluginName ==
"enabled") || (pluginName ==
"multi"))
156 plugins << pluginName.trimmed();
158 QSet<QString> categories;
159 for (
const QString &name :
asConst(plugins))
162 if (!plugin)
continue;
164 categories << category;
167 return categories.values();
185 disabledPlugins.removeAll(name);
186 else if (!disabledPlugins.contains(name))
187 disabledPlugins.append(name);
214 if (path.startsWith(
"file:", Qt::CaseInsensitive))
215 path = QUrl(path).toLocalFile();
218 pluginName.chop(pluginName.size() - pluginName.lastIndexOf(
'.'));
220 if (!path.endsWith(
".py", Qt::CaseInsensitive))
231 if (plugin && !(plugin->
version < newVersion))
233 LogMsg(tr(
"Plugin already at version %1, which is greater than %2").arg(plugin->
version, newVersion),
Log::INFO);
234 emit
pluginUpdateFailed(name, tr(
"A more recent version of this plugin is already installed."));
240 bool updated =
false;
241 if (QFile::exists(destPath))
277 LogMsg(tr(
"Plugin %1 has been successfully updated.").arg(name),
Log::INFO);
290 filters << name +
".*";
291 const QStringList
files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
305 if (QFile::exists(iconPath))
312 if (QFile::exists(iconPath))
321 DownloadManager::instance()->download({
m_updateUrl +
"versions.txt"}
333 Q_ASSERT(!pattern.isEmpty());
335 return new SearchHandler {pattern, category, usedPlugins,
this};
340 const QHash<QString, QString> categoryTable
342 {
"all", tr(
"All categories")},
343 {
"movies", tr(
"Movies")},
344 {
"tv", tr(
"TV shows")},
345 {
"music", tr(
"Music")},
346 {
"games", tr(
"Games")},
347 {
"anime", tr(
"Anime")},
348 {
"software", tr(
"Software")},
349 {
"pictures", tr(
"Pictures")},
350 {
"books", tr(
"Books")}
352 return categoryTable.value(categoryName);
367 static QString location;
368 if (location.isEmpty())
372 const QDir locationDir(location);
373 locationDir.mkpath(locationDir.absolutePath());
394 pluginName.chop(pluginName.size() - pluginName.lastIndexOf(
'.'));
400 const QString url = result.
url;
401 QString pluginName = url.mid(url.lastIndexOf(
'/') + 1);
402 pluginName.replace(
".py",
"", Qt::CaseInsensitive);
417 QFile packageFile(searchDir.absoluteFilePath(
"__init__.py"));
418 packageFile.open(QIODevice::WriteOnly);
421 searchDir.mkdir(
"engines");
423 QFile packageFile2(searchDir.absolutePath() +
"/engines/__init__.py");
424 packageFile2.open(QIODevice::WriteOnly);
425 packageFile2.close();
428 const auto updateFile = [](
const QString &filename,
const bool compareVersion)
430 const QString filePathBundled =
":/searchengine/nova3/" + filename;
431 const QString filePathDisk = QDir(
engineLocation()).absoluteFilePath(filename);
440 updateFile(
"helpers.py",
true);
441 updateFile(
"nova2.py",
true);
442 updateFile(
"nova2dl.py",
true);
443 updateFile(
"novaprinter.py",
true);
444 updateFile(
"sgmllib3.py",
false);
445 updateFile(
"socks.py",
false);
451 nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
455 nova.waitForFinished();
457 const QString capabilities = nova.readAll();
459 if (!xmlDoc.setContent(capabilities))
461 qWarning() <<
"Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
462 qWarning() <<
"Error: " << nova.readAllStandardError().constData();
466 const QDomElement root = xmlDoc.documentElement();
467 if (root.tagName() !=
"capabilities")
469 qWarning() <<
"Invalid XML file for Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
473 for (QDomNode engineNode = root.firstChild(); !engineNode.isNull(); engineNode = engineNode.nextSibling())
475 const QDomElement engineElem = engineNode.toElement();
476 if (!engineElem.isNull())
478 const QString pluginName = engineElem.tagName();
480 auto plugin = std::make_unique<PluginInfo>();
481 plugin->name = pluginName;
483 plugin->fullName = engineElem.elementsByTagName(
"name").at(0).toElement().text();
484 plugin->url = engineElem.elementsByTagName(
"url").at(0).toElement().text();
486 const QStringList categories = engineElem.elementsByTagName(
"categories").at(0).toElement().text().split(
' ');
487 for (QString cat : categories)
491 plugin->supportedCategories << cat;
495 plugin->enabled = !disabledEngines.contains(pluginName);
501 m_plugins[pluginName] = plugin.release();
504 else if (
m_plugins[pluginName]->version != plugin->version)
507 m_plugins[pluginName] = plugin.release();
516 QHash<QString, PluginVersion> updateInfo;
517 int numCorrectData = 0;
520 for (QByteArray line : lines)
522 line = line.trimmed();
523 if (line.isEmpty())
continue;
524 if (line.startsWith(
'#'))
continue;
527 if (list.size() != 2)
continue;
529 const QString pluginName = list.first().trimmed();
532 if (!version.
isValid())
continue;
537 LogMsg(tr(
"Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version),
Log::INFO);
538 updateInfo[pluginName] = version;
542 if (numCorrectData < lines.size())
545 .arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size())));
556 if (!plugin)
return true;
559 return (newVersion > oldVersion);
569 QFile pluginFile(filePath);
570 if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
573 while (!pluginFile.atEnd())
575 const QString line = QString(pluginFile.readLine()).remove(
' ');
576 if (!line.startsWith(
"#VERSION:", Qt::CaseInsensitive))
continue;
578 const QString versionStr = line.mid(9);
583 LogMsg(tr(
"Search plugin '%1' contains invalid version string ('%2')")
static bool hasSupportedScheme(const QString &url)
static Preferences * instance()
QStringList getSearchEngDisabled() const
void setSearchEngDisabled(const QStringList &engines)
bool isUpdateNeeded(const QString &pluginName, PluginVersion newVersion) const
void updatePlugin(const QString &name)
void installPlugin_impl(const QString &name, const QString &path)
PluginInfo * pluginInfo(const QString &name) const
void enablePlugin(const QString &name, bool enabled=true)
QStringList enabledPlugins() const
SearchDownloadHandler * downloadTorrent(const QString &siteUrl, const QString &url)
void pluginUpdated(const QString &name)
static QString pluginPath(const QString &name)
~SearchPluginManager() override
SearchHandler * startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins)
QString pluginFullName(const QString &pluginName)
void pluginInstallationFailed(const QString &name, const QString &reason)
static void freeInstance()
static QPointer< SearchPluginManager > m_instance
static PluginVersion getPluginVersion(const QString &filePath)
const QString m_updateUrl
static void updateIconPath(PluginInfo *const plugin)
QHash< QString, PluginInfo * > m_plugins
static QString categoryFullName(const QString &categoryName)
QStringList getPluginCategories(const QString &pluginName) const
static QString engineLocation()
void pluginEnabled(const QString &name, bool enabled)
void versionInfoDownloadFinished(const Net::DownloadResult &result)
QStringList supportedCategories() const
static QString pluginsLocation()
void pluginInstalled(const QString &name)
QStringList allPlugins() const
void pluginUninstalled(const QString &name)
void installPlugin(const QString &source)
void checkForUpdatesFinished(const QHash< QString, PluginVersion > &updateInfo)
void checkForUpdatesFailed(const QString &reason)
static SearchPluginManager * instance()
bool uninstallPlugin(const QString &name)
void parseVersionInfo(const QByteArray &info)
void pluginUpdateFailed(const QString &name, const QString &reason)
void pluginDownloadFinished(const Net::DownloadResult &result)
static Version tryParse(const StringClassWithSplitMethod &s, const Version &defaultVersion)
constexpr bool isValid() const
constexpr std::add_const_t< T > & asConst(T &t) noexcept
flag icons free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to copy
flag icons free of to any person obtaining a copy of this software and associated documentation files(the "Software")
void LogMsg(const QString &message, const Log::MsgType &type)
QVector< QByteArray > splitToViews(const QByteArray &in, const QByteArray &sep, const Qt::SplitBehavior behavior=Qt::KeepEmptyParts)
QString fileName(const QString &filePath)
void removeDirRecursive(const QString &path)
QString toUniformPath(const QString &path)
QString expandPathAbs(const QString &path)
bool forceRemove(const QString &filePath)
QString toNativePath(const QString &path)
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
void clearPythonCache(const QString &path)
QString specialFolderLocation(const SpecialFolder folder)
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
QStringList supportedCategories