File indexing completed on 2024-05-12 04:37:48
0001 /* 0002 SPDX-FileCopyrightText: 2007 Alexander Dymo <adymo@kdevelop.org> 0003 SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "templatesmodel.h" 0009 0010 #include "templatepreviewicon.h" 0011 #include <debug.h> 0012 #include <interfaces/icore.h> 0013 0014 #include <KConfig> 0015 #include <KTar> 0016 #include <KZip> 0017 #include <KConfigGroup> 0018 #include <KLocalizedString> 0019 0020 #include <QMimeType> 0021 #include <QMimeDatabase> 0022 #include <QFileInfo> 0023 #include <QDir> 0024 #include <QStandardPaths> 0025 #include <QTemporaryDir> 0026 0027 using namespace KDevelop; 0028 0029 class KDevelop::TemplatesModelPrivate 0030 { 0031 public: 0032 explicit TemplatesModelPrivate(const QString& typePrefix); 0033 0034 QString typePrefix; 0035 0036 QStringList searchPaths; 0037 0038 QMap<QString, QStandardItem*> templateItems; 0039 0040 /** 0041 * Extracts description files from all available template archives and saves them to a location 0042 * determined by descriptionResourceSuffix(). 0043 **/ 0044 void extractTemplateDescriptions(); 0045 0046 /** 0047 * Creates a model item for the template @p name in category @p category 0048 * 0049 * @param name the name of the new template 0050 * @param category the category of the new template 0051 * @param parent the parent item 0052 * @return the created item 0053 **/ 0054 QStandardItem* createItem(const QString& name, const QString& category, QStandardItem* parent); 0055 0056 enum ResourceType 0057 { 0058 Description, 0059 Template, 0060 Preview 0061 }; 0062 QString resourceFilter(ResourceType type, const QString& suffix = QString()) const 0063 { 0064 QString filter = typePrefix; 0065 switch (type) { 0066 case Description: 0067 filter += QLatin1String("template_descriptions/"); 0068 break; 0069 case Template: 0070 filter += QLatin1String("templates/"); 0071 break; 0072 case Preview: 0073 filter += QLatin1String("template_previews/"); 0074 break; 0075 } 0076 return filter + suffix; 0077 } 0078 }; 0079 0080 TemplatesModelPrivate::TemplatesModelPrivate(const QString& _typePrefix) 0081 : typePrefix(_typePrefix) 0082 { 0083 if (!typePrefix.endsWith(QLatin1Char('/'))) { 0084 typePrefix.append(QLatin1Char('/')); 0085 } 0086 } 0087 0088 TemplatesModel::TemplatesModel(const QString& typePrefix, QObject* parent) 0089 : QStandardItemModel(parent) 0090 , d_ptr(new TemplatesModelPrivate(typePrefix)) 0091 { 0092 } 0093 0094 TemplatesModel::~TemplatesModel() = default; 0095 0096 void TemplatesModel::refresh() 0097 { 0098 Q_D(TemplatesModel); 0099 0100 clear(); 0101 d->templateItems.clear(); 0102 d->templateItems[QString()] = invisibleRootItem(); 0103 d->extractTemplateDescriptions(); 0104 0105 QStringList templateArchives; 0106 for (const QString& archivePath : qAsConst(d->searchPaths)) { 0107 const QStringList files = QDir(archivePath).entryList(QDir::Files); 0108 for (const QString& file : files) { 0109 templateArchives.append(archivePath + file); 0110 } 0111 } 0112 0113 QStringList templateDescriptions; 0114 const QStringList templatePaths = 0115 {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->resourceFilter( 0116 TemplatesModelPrivate::Description)}; 0117 for (const QString& templateDescription : templatePaths) { 0118 const QStringList files = QDir(templateDescription).entryList(QDir::Files); 0119 for (const QString& file : files) { 0120 templateDescriptions.append(templateDescription + file); 0121 } 0122 } 0123 0124 for (const QString& templateDescription : qAsConst(templateDescriptions)) { 0125 QFileInfo fi(templateDescription); 0126 bool archiveFound = false; 0127 for (const QString& templateArchive : qAsConst(templateArchives)) { 0128 if (QFileInfo(templateArchive).baseName() == fi.baseName()) { 0129 archiveFound = true; 0130 0131 KConfig templateConfig(templateDescription); 0132 KConfigGroup general(&templateConfig, "General"); 0133 QString name = general.readEntry("Name"); 0134 QString category = general.readEntry("Category"); 0135 QString comment = general.readEntry("Comment"); 0136 TemplatePreviewIcon icon(general.readEntry("Icon"), templateArchive, d->resourceFilter( 0137 TemplatesModelPrivate::Preview)); 0138 0139 QStandardItem* templateItem = d->createItem(name, category, invisibleRootItem()); 0140 templateItem->setData(templateDescription, DescriptionFileRole); 0141 templateItem->setData(templateArchive, ArchiveFileRole); 0142 templateItem->setData(comment, CommentRole); 0143 templateItem->setData(QVariant::fromValue<TemplatePreviewIcon>(icon), PreviewIconRole); 0144 } 0145 } 0146 0147 if (!archiveFound) { 0148 // Template file doesn't exist anymore, so remove the description 0149 // saves us the extra lookups for templateExists on the next run 0150 QFile(templateDescription).remove(); 0151 } 0152 } 0153 } 0154 0155 QStandardItem* TemplatesModelPrivate::createItem(const QString& name, const QString& category, QStandardItem* parent) 0156 { 0157 const QStringList path = category.split(QLatin1Char('/')); 0158 0159 QStringList currentPath; 0160 currentPath.reserve(path.size()); 0161 for (const QString& entry : path) { 0162 currentPath << entry; 0163 if (!templateItems.contains(currentPath.join(QLatin1Char('/')))) { 0164 auto* item = new QStandardItem(entry); 0165 item->setEditable(false); 0166 parent->appendRow(item); 0167 templateItems[currentPath.join(QLatin1Char('/'))] = item; 0168 parent = item; 0169 } else { 0170 parent = templateItems[currentPath.join(QLatin1Char('/'))]; 0171 } 0172 } 0173 0174 auto* templateItem = new QStandardItem(name); 0175 templateItem->setEditable(false); 0176 parent->appendRow(templateItem); 0177 return templateItem; 0178 } 0179 0180 void TemplatesModelPrivate::extractTemplateDescriptions() 0181 { 0182 QStringList templateArchives; 0183 searchPaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, resourceFilter( 0184 Template), QStandardPaths::LocateDirectory); 0185 searchPaths.removeDuplicates(); 0186 for (const QString& archivePath : qAsConst(searchPaths)) { 0187 const QStringList files = QDir(archivePath).entryList(QDir::Files); 0188 for (const QString& file : files) { 0189 if (file.endsWith(QLatin1String(".zip")) || file.endsWith(QLatin1String(".tar.bz2"))) { 0190 QString archfile = archivePath + file; 0191 templateArchives.append(archfile); 0192 } 0193 } 0194 } 0195 0196 QString localDescriptionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char( 0197 '/') + resourceFilter(Description); 0198 0199 QDir dir(localDescriptionsDir); 0200 if (!dir.exists()) 0201 dir.mkpath(QStringLiteral(".")); 0202 0203 for (const QString& archName : qAsConst(templateArchives)) { 0204 qCDebug(LANGUAGE) << "processing template" << archName; 0205 0206 QScopedPointer<KArchive> templateArchive; 0207 if (QFileInfo(archName).completeSuffix() == QLatin1String("zip")) { 0208 templateArchive.reset(new KZip(archName)); 0209 } else 0210 { 0211 templateArchive.reset(new KTar(archName)); 0212 } 0213 0214 if (templateArchive->open(QIODevice::ReadOnly)) { 0215 /* 0216 * This class looks for template description files in the following order 0217 * 0218 * - "basename.kdevtemplate" 0219 * - "*.kdevtemplate" 0220 * - "basename.desktop" 0221 * - "*.desktop" 0222 * 0223 * This is done because application templates can contain .desktop files used by the application 0224 * so the kdevtemplate suffix must have priority. 0225 */ 0226 QFileInfo templateInfo(archName); 0227 QString suffix = QStringLiteral(".kdevtemplate"); 0228 const KArchiveEntry* templateEntry = 0229 templateArchive->directory()->entry(templateInfo.baseName() + suffix); 0230 0231 if (!templateEntry || !templateEntry->isFile()) { 0232 /* 0233 * First, if the .kdevtemplate file is not found by name, 0234 * we check all the files in the archive for any .kdevtemplate file 0235 * 0236 * This is needed because kde-files.org renames downloaded files 0237 */ 0238 const auto dirEntries = templateArchive->directory()->entries(); 0239 for (const QString& entryName : dirEntries) { 0240 if (entryName.endsWith(suffix)) { 0241 templateEntry = templateArchive->directory()->entry(entryName); 0242 break; 0243 } 0244 } 0245 } 0246 0247 if (!templateEntry || !templateEntry->isFile()) { 0248 suffix = QStringLiteral(".desktop"); 0249 templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + suffix); 0250 } 0251 0252 if (!templateEntry || !templateEntry->isFile()) { 0253 const auto dirEntries = templateArchive->directory()->entries(); 0254 for (const QString& entryName : dirEntries) { 0255 if (entryName.endsWith(suffix)) { 0256 templateEntry = templateArchive->directory()->entry(entryName); 0257 break; 0258 } 0259 } 0260 } 0261 if (!templateEntry || !templateEntry->isFile()) { 0262 qCDebug(LANGUAGE) << "template" << archName << "does not contain .kdevtemplate or .desktop file"; 0263 continue; 0264 } 0265 const auto* templateFile = static_cast<const KArchiveFile*>(templateEntry); 0266 0267 qCDebug(LANGUAGE) << "copy template description to" << localDescriptionsDir; 0268 const QString descriptionFileName = templateInfo.baseName() + suffix; 0269 if (templateFile->name() == descriptionFileName) { 0270 templateFile->copyTo(localDescriptionsDir); 0271 } else { 0272 // Rename the extracted description 0273 // so that its basename matches the basename of the template archive 0274 // Use temporary dir to not overwrite other files with same name 0275 QTemporaryDir dir; 0276 templateFile->copyTo(dir.path()); 0277 const QString destinationPath = localDescriptionsDir + descriptionFileName; 0278 QFile::remove(destinationPath); 0279 QFile::rename(dir.path() + QLatin1Char('/') + templateFile->name(), destinationPath); 0280 } 0281 } else 0282 { 0283 qCWarning(LANGUAGE) << "could not open template" << archName << ':' << templateArchive->errorString(); 0284 } 0285 } 0286 } 0287 0288 QModelIndexList TemplatesModel::templateIndexes(const QString& fileName) const 0289 { 0290 Q_D(const TemplatesModel); 0291 0292 QFileInfo info(fileName); 0293 QString description = 0294 QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0295 d->resourceFilter(TemplatesModelPrivate::Description, 0296 info.baseName() + QLatin1String(".kdevtemplate"))); 0297 if (description.isEmpty()) { 0298 description = 0299 QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0300 d->resourceFilter(TemplatesModelPrivate::Description, 0301 info.baseName() + QLatin1String(".desktop"))); 0302 } 0303 0304 QModelIndexList indexes; 0305 0306 if (!description.isEmpty()) { 0307 KConfig templateConfig(description); 0308 KConfigGroup general(&templateConfig, "General"); 0309 const QStringList categories = general.readEntry("Category").split(QLatin1Char('/')); 0310 0311 QStringList levels; 0312 levels.reserve(categories.size()); 0313 for (const QString& category : categories) { 0314 levels << category; 0315 indexes << d->templateItems[levels.join(QLatin1Char('/'))]->index(); 0316 } 0317 0318 if (!indexes.isEmpty()) { 0319 QString name = general.readEntry("Name"); 0320 QStandardItem* categoryItem = d->templateItems[levels.join(QLatin1Char('/'))]; 0321 for (int i = 0; i < categoryItem->rowCount(); ++i) { 0322 QStandardItem* templateItem = categoryItem->child(i); 0323 if (templateItem->text() == name) { 0324 indexes << templateItem->index(); 0325 break; 0326 } 0327 } 0328 } 0329 } 0330 0331 return indexes; 0332 } 0333 0334 QString TemplatesModel::typePrefix() const 0335 { 0336 Q_D(const TemplatesModel); 0337 0338 return d->typePrefix; 0339 } 0340 0341 void TemplatesModel::addDataPath(const QString& path) 0342 { 0343 Q_D(TemplatesModel); 0344 0345 QString realpath = path + d->resourceFilter(TemplatesModelPrivate::Template); 0346 d->searchPaths.append(realpath); 0347 } 0348 0349 QString TemplatesModel::loadTemplateFile(const QString& fileName) 0350 { 0351 Q_D(TemplatesModel); 0352 0353 QString saveLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + 0354 d->resourceFilter(TemplatesModelPrivate::Template); 0355 0356 QDir dir(saveLocation); 0357 if (!dir.exists()) 0358 dir.mkpath(QStringLiteral(".")); 0359 0360 QFileInfo info(fileName); 0361 QString destination = saveLocation + info.baseName(); 0362 0363 QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileName); 0364 qCDebug(LANGUAGE) << "Loaded file" << fileName << "with type" << mimeType.name(); 0365 0366 if (mimeType.name() == QLatin1String("application/x-desktop")) { 0367 qCDebug(LANGUAGE) << "Loaded desktop file" << info.absoluteFilePath() << ", compressing"; 0368 #ifdef Q_WS_WIN 0369 destination += ".zip"; 0370 KZip archive(destination); 0371 #else 0372 destination += QLatin1String(".tar.bz2"); 0373 KTar archive(destination, QStringLiteral("application/x-bzip")); 0374 #endif //Q_WS_WIN 0375 0376 archive.open(QIODevice::WriteOnly); 0377 0378 QDir dir(info.absoluteDir()); 0379 const auto dirEntryInfos = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); 0380 for (const QFileInfo& entry : dirEntryInfos) { 0381 if (entry.isFile()) { 0382 archive.addLocalFile(entry.absoluteFilePath(), entry.fileName()); 0383 } else if (entry.isDir()) { 0384 archive.addLocalDirectory(entry.absoluteFilePath(), entry.fileName()); 0385 } 0386 } 0387 0388 archive.close(); 0389 } else 0390 { 0391 qCDebug(LANGUAGE) << "Copying" << fileName << "to" << saveLocation; 0392 QFile::copy(fileName, saveLocation + info.fileName()); 0393 } 0394 0395 refresh(); 0396 0397 return destination; 0398 } 0399 0400 #include "moc_templatesmodel.cpp"