void Device::reloadPlugins() { QHash<QString, KdeConnectPlugin*> newPluginMap; QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingInterface; QMultiMap<QString, KdeConnectPlugin*> newPluginsByOutgoingInterface; QSet<QString> supportedIncomingInterfaces; QSet<QString> supportedOutgoingInterfaces; QStringList unsupportedPlugins; if (isPaired() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); PluginLoader* loader = PluginLoader::instance(); const bool deviceSupportsCapabilities = !m_incomingCapabilities.isEmpty() || !m_outgoingCapabilities.isEmpty(); foreach (const QString& pluginName, loader->getPluginList()) { const KPluginMetaData service = loader->getPluginInfo(pluginName); const QSet<QString> incomingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet(); const QSet<QString> outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType").toSet(); const bool pluginEnabled = isPluginEnabled(pluginName); if (pluginEnabled) { supportedIncomingInterfaces += incomingInterfaces; supportedOutgoingInterfaces += outgoingInterfaces; } //If we don't find intersection with the received on one end and the sent on the other, we don't //let the plugin stay //Also, if no capabilities are specified on the other end, we don't apply this optimizaton, as //we assume that the other client doesn't know about capabilities. const bool capabilitiesSupported = deviceSupportsCapabilities && (!incomingInterfaces.isEmpty() || !outgoingInterfaces.isEmpty()); if (capabilitiesSupported && (m_incomingCapabilities & outgoingInterfaces).isEmpty() && (m_outgoingCapabilities & incomingInterfaces).isEmpty() ) { qCWarning(KDECONNECT_CORE) << "not loading " << pluginName << "because of unmatched capabilities"; unsupportedPlugins.append(pluginName); continue; } if (pluginEnabled) { KdeConnectPlugin* plugin = m_plugins.take(pluginName); if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } foreach(const QString& interface, incomingInterfaces) { newPluginsByIncomingInterface.insert(interface, plugin); } foreach(const QString& interface, outgoingInterfaces) { newPluginsByOutgoingInterface.insert(interface, plugin); } newPluginMap[pluginName] = plugin; }
void Device::reloadPlugins() { QHash<QString, KdeConnectPlugin*> newPluginMap; QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingInterface; QMultiMap<QString, KdeConnectPlugin*> newPluginsByOutgoingInterface; if (isPaired() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); PluginLoader* loader = PluginLoader::instance(); //Code borrowed from KWin foreach (const QString& pluginName, loader->getPluginList()) { QString enabledKey = pluginName + QString::fromLatin1("Enabled"); bool isPluginEnabled = (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false) : loader->getPluginInfo(pluginName).isEnabledByDefault()); if (isPluginEnabled) { KdeConnectPlugin* plugin = m_plugins.take(pluginName); QStringList incomingInterfaces, outgoingInterfaces; if (plugin) { incomingInterfaces = m_pluginsByIncomingInterface.keys(plugin); outgoingInterfaces = m_pluginsByOutgoingInterface.keys(plugin); } else { const KPluginMetaData service = loader->getPluginInfo(pluginName); incomingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType"); outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType"); } //If we don't find intersection with the received on one end and the sent on the other, we don't //let the plugin stay //Also, if no capabilities are specified on the other end, we don't apply this optimizaton, as //we assume that the other client doesn't know about capabilities. if (!m_incomingCapabilities.isEmpty() && !m_outgoingCapabilities.isEmpty() && (m_incomingCapabilities & outgoingInterfaces.toSet()).isEmpty() && (m_outgoingCapabilities & incomingInterfaces.toSet()).isEmpty() ) { delete plugin; continue; } if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } foreach(const QString& interface, incomingInterfaces) { newPluginsByIncomingInterface.insert(interface, plugin); } foreach(const QString& interface, outgoingInterfaces) { newPluginsByOutgoingInterface.insert(interface, plugin); } newPluginMap[pluginName] = plugin; }
bool PackageJobThread::installPackage(const QString &src, const QString &dest, OperationType operation) { QDir root(dest); if (!root.exists()) { QDir().mkpath(dest); if (!root.exists()) { d->errorMessage = i18n("Could not create package root directory: %1", dest); d->errorCode = Package::JobError::RootCreationError; //qWarning() << "Could not create package root directory: " << dest; return false; } } QFileInfo fileInfo(src); if (!fileInfo.exists()) { d->errorMessage = i18n("No such file: %1", src); d->errorCode = Package::JobError::PackageFileNotFoundError; return false; } QString path; QTemporaryDir tempdir; bool archivedPackage = false; if (fileInfo.isDir()) { // we have a directory, so let's just install what is in there path = src; // make sure we end in a slash! if (!path.endsWith('/')) { path.append('/'); } } else { KArchive *archive = 0; QMimeDatabase db; QMimeType mimetype = db.mimeTypeForFile(src); if (mimetype.inherits(QStringLiteral("application/zip"))) { archive = new KZip(src); } else if (mimetype.inherits(QStringLiteral("application/x-compressed-tar")) || mimetype.inherits(QStringLiteral("application/x-tar")) || mimetype.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mimetype.inherits(QStringLiteral("application/x-xz")) || mimetype.inherits(QStringLiteral("application/x-lzma"))) { archive = new KTar(src); } else { //qWarning() << "Could not open package file, unsupported archive format:" << src << mimetype.name(); d->errorMessage = i18n("Could not open package file, unsupported archive format: %1 %2", src, mimetype.name()); d->errorCode = Package::JobError::UnsupportedArchiveFormatError; return false; } if (!archive->open(QIODevice::ReadOnly)) { //qWarning() << "Could not open package file:" << src; delete archive; d->errorMessage = i18n("Could not open package file: %1", src); d->errorCode = Package::JobError::PackageOpenError; return false; } archivedPackage = true; path = tempdir.path() + '/'; d->installPath = path; const KArchiveDirectory *source = archive->directory(); source->copyTo(path); QStringList entries = source->entries(); if (entries.count() == 1) { const KArchiveEntry *entry = source->entry(entries[0]); if (entry->isDirectory()) { path.append(entry->name()).append("/"); } } delete archive; } QDir packageDir(path); QFileInfoList entries = packageDir.entryInfoList(*metaDataFiles); KPluginMetaData meta; if (!entries.isEmpty()) { const QString metadataFilePath = entries.first().filePath(); if (metadataFilePath.endsWith(QLatin1String(".desktop"))) meta = KPluginMetaData(metadataFilePath); else { QFile f(metadataFilePath); if(!f.open(QIODevice::ReadOnly)){ qWarning() << "Couldn't open metadata file" << src << path; d->errorMessage = i18n("Could not open metadata file: %1", src); d->errorCode = Package::JobError::MetadataFileMissingError; return false; } QJsonObject metadataObject = QJsonDocument::fromJson(f.readAll()).object(); meta = KPluginMetaData(metadataObject, QString(), metadataFilePath); } } if (!meta.isValid()) { qDebug() << "No metadata file in package" << src << path; d->errorMessage = i18n("No metadata file in package: %1", src); d->errorCode = Package::JobError::MetadataFileMissingError; return false; } QString pluginName = meta.pluginId(); qDebug() << "pluginname: " << meta.pluginId(); if (pluginName.isEmpty()) { //qWarning() << "Package plugin name not specified"; d->errorMessage = i18n("Package plugin name not specified: %1", src); d->errorCode = Package::JobError::PluginNameMissingError; return false; } // Ensure that package names are safe so package uninstall can't inject // bad characters into the paths used for removal. QRegExp validatePluginName("^[\\w-\\.]+$"); // Only allow letters, numbers, underscore and period. if (!validatePluginName.exactMatch(pluginName)) { //qDebug() << "Package plugin name " << pluginName << "contains invalid characters"; d->errorMessage = i18n("Package plugin name %1 contains invalid characters", pluginName); d->errorCode = Package::JobError::PluginNameInvalidError; return false; } QString targetName = dest; if (targetName[targetName.size() - 1] != '/') { targetName.append('/'); } targetName.append(pluginName); if (QFile::exists(targetName)) { if (operation == Update) { KPluginMetaData oldMeta(targetName + QLatin1String("/metadata.desktop")); if (oldMeta.serviceTypes() != meta.serviceTypes()) { d->errorMessage = i18n("The new package has a different type from the old version already installed.", meta.version(), meta.pluginId(), oldMeta.version()); d->errorCode = Package::JobError::UpdatePackageTypeMismatchError; } else if (isVersionNewer(oldMeta.version(), meta.version())) { const bool ok = uninstallPackage(targetName); if (!ok) { d->errorMessage = i18n("Impossible to remove the old installation of %1 located at %2. error: %3", pluginName, targetName, d->errorMessage); d->errorCode = Package::JobError::OldVersionRemovalError; } } else { d->errorMessage = i18n("Not installing version %1 of %2. Version %3 already installed.", meta.version(), meta.pluginId(), oldMeta.version()); d->errorCode = Package::JobError::NewerVersionAlreadyInstalledError; } } else { d->errorMessage = i18n("%1 already exists", targetName); d->errorCode = Package::JobError::PackageAlreadyInstalledError; } if (d->errorCode != KJob::NoError) { d->installPath = targetName; return false; } } //install dependencies const QStringList dependencies = KPluginMetaData::readStringList(meta.rawData(), QStringLiteral("X-KPackage-Dependencies")); for(const QString &dep : dependencies) { QUrl depUrl(dep); if (!installDependency(depUrl)) { d->errorMessage = i18n("Could not install dependency: %1", dep); d->errorCode = Package::JobError::PackageCopyError; return false; } } if (archivedPackage) { // it's in a temp dir, so just move it over. const bool ok = copyFolder(path, targetName); removeFolder(path); if (!ok) { //qWarning() << "Could not move package to destination:" << targetName; d->errorMessage = i18n("Could not move package to destination: %1", targetName); d->errorCode = Package::JobError::PackageMoveError; return false; } } else { // it's a directory containing the stuff, so copy the contents rather // than move them const bool ok = copyFolder(path, targetName); if (!ok) { //qWarning() << "Could not copy package to destination:" << targetName; d->errorMessage = i18n("Could not copy package to destination: %1", targetName); d->errorCode = Package::JobError::PackageCopyError; return false; } } if (archivedPackage) { // no need to remove the temp dir (which has been successfully moved if it's an archive) tempdir.setAutoRemove(false); } indexDirectory(dest, QStringLiteral("kpluginindex.json")); d->installPath = targetName; //qWarning() << "Not updating kbuildsycoca4, since that will go away. Do it yourself for now if needed."; return true; }