File indexing completed on 2024-04-14 05:19:08
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2020 Aditya Mehra <aix.m@outlook.com> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include <QJsonDocument> 0008 #include <QJsonObject> 0009 #include <QJsonArray> 0010 #include <QNetworkAccessManager> 0011 #include <QNetworkReply> 0012 #include <QEventLoop> 0013 #include "installerlistmodel.h" 0014 #include "processcommander.h" 0015 #include "globalconfiguration.h" 0016 #include <QFile> 0017 #include <QDir> 0018 0019 0020 InstallerListModel::InstallerListModel(QObject *parent) : QAbstractListModel(parent) 0021 { 0022 setCategoryBrowser(0); 0023 m_blackList.append("1490534"); 0024 m_blackList.append("1193621"); 0025 m_globalConfiguration = new GlobalConfiguration(this); 0026 m_selectedBackendType = m_globalConfiguration->backendConfig(); 0027 m_selectedBackendXdgSupport = m_globalConfiguration->backendConfigXDGSupported(); 0028 } 0029 0030 void InstallerListModel::reloadJsonModel() 0031 { 0032 m_jsonList.clear(); 0033 while(m_combinedDoc.count()) { 0034 m_combinedDoc.pop_back(); 0035 } 0036 if(!m_reloadingModel){ 0037 setReloadModel(); 0038 } 0039 } 0040 0041 void InstallerListModel::setReloadModel() 0042 { 0043 m_reloadingModel = true; 0044 setUrl(m_url); 0045 } 0046 0047 QString InstallerListModel::json() const 0048 { 0049 return m_json; 0050 } 0051 0052 QString InstallerListModel::url() const 0053 { 0054 return m_url; 0055 } 0056 0057 void InstallerListModel::setUrl(const QString &url) 0058 { 0059 if(!url.isEmpty()) { 0060 m_jsonList.clear(); 0061 while(m_combinedDoc.count()) { 0062 m_combinedDoc.pop_back(); 0063 } 0064 setDownloadingModel(true); 0065 m_originalCount = 0; 0066 QNetworkAccessManager *manager = new QNetworkAccessManager(this); 0067 QNetworkRequest request; 0068 const QUrl qurl = url; 0069 m_url = url; 0070 request.setUrl(qurl); 0071 QNetworkReply *reply = manager->get(request); 0072 connect(reply, &QNetworkReply::finished, [this, reply, url] () { 0073 QByteArray response_data = reply->readAll(); 0074 getSecondaryJson(response_data, url); 0075 reply->close(); 0076 }); 0077 } 0078 } 0079 0080 void InstallerListModel::getSecondaryJson(const QByteArray &data, const QString &url) 0081 { 0082 QNetworkAccessManager *manager = new QNetworkAccessManager(this); 0083 QNetworkRequest request; 0084 QJsonDocument json = QJsonDocument::fromJson(data); 0085 QJsonObject r = json.object(); 0086 QJsonArray storeItems = r.value("data").toArray(); 0087 // qDebug() << "Store Items Count Before Invalid" << storeItems.count(); 0088 for(int s = 0; s < storeItems.count(); s++) { 0089 QJsonObject storeItem = storeItems[s].toObject(); 0090 QString id = storeItem["id"].toString(); 0091 QString download_name = storeItem["downloadname1"].toString(); 0092 if(download_name != "skill.json"){ 0093 storeItems.removeAt(s); 0094 } 0095 if(m_blackList.contains(id)){ 0096 storeItems.removeAt(s); 0097 } 0098 } 0099 // qDebug() << "Store Items Count After Invalid" << storeItems.count(); 0100 0101 m_originalCount = storeItems.count(); 0102 0103 QFile cacheFile(m_cacheDataPath); 0104 int cacheItemCount = 0; 0105 QJsonDocument d; 0106 0107 if (url == getCurrentCategory() && cacheFile.size() > 0) { 0108 cacheFile.open(QFile::ReadOnly | QFile::Text); 0109 QString val = cacheFile.readAll(); 0110 cacheFile.close(); 0111 d = QJsonDocument::fromJson(val.toUtf8()); 0112 0113 QJsonObject r = d.object(); 0114 QJsonArray cacheItems = r.value("data").toArray(); 0115 cacheItemCount = cacheItems.count(); 0116 } else { 0117 cacheItemCount = 0; 0118 d = QJsonDocument::fromJson({}); 0119 } 0120 0121 if(cacheItemCount == m_originalCount){ 0122 QJsonObject z = d.object(); 0123 QJsonArray zItems = z.value("data").toArray(); 0124 QJsonArray updateCacheArray; 0125 0126 for(int i = 0; i < zItems.count(); i++){ 0127 QJsonObject eachCacheItem = zItems[i].toObject(); 0128 QString skillName = eachCacheItem["skillname"].toString().toLower(); 0129 QString skillAuthor = eachCacheItem["authorname"].toString().toLower(); 0130 QString skillUrl = eachCacheItem["url"].toString() + ".git"; 0131 QString skillBranch = eachCacheItem["branch"].toString(); 0132 0133 QString skillPath = skillName + "." + skillAuthor; 0134 0135 bool zItems_installed = checkInstalled(skillName, skillAuthor); 0136 eachCacheItem.remove("itemInstallStatus"); 0137 eachCacheItem.insert("itemInstallStatus", zItems_installed); 0138 if (zItems_installed) { 0139 bool zItems_update_available = checkUpdatesAvailable(skillUrl, skillBranch, skillPath); 0140 eachCacheItem.remove("itemUpdateStatus"); 0141 eachCacheItem.insert("itemUpdateStatus", zItems_update_available); 0142 } 0143 updateCacheArray.append(eachCacheItem); 0144 } 0145 0146 QJsonObject updatedCacheObject; 0147 updatedCacheObject.insert("data", updateCacheArray); 0148 QJsonDocument cacheDoc(updatedCacheObject); 0149 0150 QFile file(m_cacheDataPath); 0151 file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); 0152 file.write(cacheDoc.toJson()); 0153 file.close(); 0154 0155 setJson(cacheDoc.toJson()); 0156 } else { 0157 for(int i = 0; i < storeItems.count(); i++) { 0158 QJsonObject storeItem = storeItems[i].toObject(); 0159 QString download_url = storeItem["downloadlink1"].toString(); 0160 QString json_response; 0161 request.setUrl(download_url); 0162 QNetworkReply *reply = manager->get(request); 0163 connect(reply, &QNetworkReply::finished, [this, reply, storeItem, manager] () { 0164 if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { 0165 QByteArray response_data = reply->readAll(); 0166 QJsonDocument json_second = QJsonDocument::fromJson(response_data); 0167 buildJSONModel(storeItem, json_second); 0168 reply->close(); 0169 } 0170 // if status code is 302 then redirect to the new location 0171 else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) { 0172 QString redirect_url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); 0173 QNetworkRequest request; 0174 request.setUrl(redirect_url); 0175 QNetworkReply *reply = manager->get(request); 0176 connect(reply, &QNetworkReply::finished, [this, reply, storeItem, manager] () { 0177 QByteArray response_data = reply->readAll(); 0178 QJsonDocument json_second = QJsonDocument::fromJson(response_data); 0179 buildJSONModel(storeItem, json_second); 0180 reply->close(); 0181 }); 0182 } 0183 }); 0184 } 0185 } 0186 setDownloadingModel(false); 0187 m_reloadingModel = false; 0188 } 0189 0190 void InstallerListModel::buildJSONModel(const QJsonObject json_one, const QJsonDocument json_two) 0191 { 0192 setCreatingModel(true); 0193 0194 QJsonObject json_obj_one = json_two.object(); 0195 QJsonObject json_obj_tow = json_one; 0196 0197 QString skillName = json_obj_one["skillname"].toString().toLower(); 0198 QString skillAuthor = json_obj_one["authorname"].toString().toLower(); 0199 QString skillBranch = json_obj_one["branch"].toString(); 0200 QString skillUrl = json_obj_one["url"].toString() + ".git"; 0201 0202 bool skillInstallCheck = checkInstalled(skillName, skillAuthor); 0203 json_obj_one.insert("itemInstallStatus", skillInstallCheck); 0204 0205 QString skillPath = skillName + "." + skillAuthor; 0206 if(skillInstallCheck) { 0207 bool skillUpdateAvailable = checkUpdatesAvailable(skillUrl, skillBranch, skillPath); 0208 json_obj_one.insert("itemUpdateStatus", skillUpdateAvailable); 0209 } else { 0210 json_obj_one.insert("itemUpdateStatus", false); 0211 } 0212 0213 QVariantMap map = json_obj_one.toVariantMap(); 0214 map.insert(json_obj_tow.toVariantMap()); 0215 0216 QJsonObject cmdJson = QJsonObject::fromVariantMap(map); 0217 m_combinedDoc.append(cmdJson); 0218 0219 // qDebug() << "Combining Json For " << skillName << m_combinedDoc.count(); 0220 m_updatingCount = m_combinedDoc.count(); 0221 0222 if(m_combinedDoc.count() == m_originalCount){ 0223 QJsonObject recordObject; 0224 recordObject.insert("data", m_combinedDoc); 0225 0226 QJsonDocument doc(recordObject); 0227 0228 QFile file(m_cacheDataPath); 0229 file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); 0230 file.write(doc.toJson()); 0231 file.close(); 0232 0233 setJson(doc.toJson()); 0234 } 0235 setCreatingModel(false); 0236 } 0237 0238 void InstallerListModel::setJson(const QString &json) 0239 { 0240 m_json = json; 0241 QJsonDocument jsonDoc = QJsonDocument::fromJson(m_json.toUtf8()); 0242 QJsonObject jsonObj = jsonDoc.object(); 0243 QJsonArray jsonArray = jsonObj[m_query].toArray(); 0244 foreach (const QJsonValue & value, jsonArray) { 0245 QJsonObject obj = value.toObject(); 0246 QHash<int, QVariant> jsonData; 0247 beginInsertRows(QModelIndex(), m_jsonList.size(), m_jsonList.size()); 0248 for ( int i = 0; i < m_roles.size(); i++ ) { 0249 jsonData.insert(i, obj[m_roles[i]].toVariant()); 0250 } 0251 m_jsonList.append(jsonData); 0252 endInsertRows(); 0253 } 0254 emit modelUpdated(); 0255 } 0256 0257 bool InstallerListModel::checkInstalled(const QString skillname, const QString skillauthor) 0258 { 0259 QStringList skillPaths = {"/opt/mycroft/skills"}; 0260 if (m_selectedBackendXdgSupport) { 0261 skillPaths.append("/home/" + m_globalConfiguration->getSystemUser() + "/.local/share/mycroft/skills"); 0262 skillPaths.append("/usr/local/share/mycroft/skills"); 0263 skillPaths.append("/usr/share/mycroft/skills"); 0264 } 0265 foreach (const QString & skillPath, skillPaths) { 0266 QDir skillDir(skillPath + "/" + skillname + "." + skillauthor); 0267 if (skillDir.exists()) { 0268 return true; 0269 } 0270 } 0271 return false; 0272 } 0273 0274 bool InstallerListModel::checkUpdatesAvailable(const QString url, const QString branch, const QString skillpath) 0275 { 0276 ProcessCommander *m_proc = new ProcessCommander; 0277 QStringList dirPaths = {"/opt/mycroft/skills"}; 0278 if (m_selectedBackendXdgSupport) { 0279 dirPaths.append("/home/" + m_globalConfiguration->getSystemUser() + "/.local/share/mycroft/skills"); 0280 dirPaths.append("/usr/local/share/mycroft/skills"); 0281 dirPaths.append("/usr/share/mycroft/skills"); 0282 } 0283 0284 bool updateAvailable = false; 0285 0286 for (int i = 0; i < dirPaths.count(); i++) { 0287 if(QFile::exists(dirPaths[i] + "/" + skillpath + "/" + "__init__.py")) { 0288 QString pathToCheck = dirPaths[i] + "/" + skillpath; 0289 updateAvailable = m_proc->runUpdateCheck(url, branch, skillpath); 0290 } 0291 } 0292 0293 return updateAvailable; 0294 } 0295 0296 QString InstallerListModel::query() const 0297 { 0298 return m_query; 0299 } 0300 0301 void InstallerListModel::setQuery(const QString &query) 0302 { 0303 m_query = query; 0304 } 0305 0306 QStringList InstallerListModel::roles() const 0307 { 0308 return m_roles; 0309 } 0310 0311 void InstallerListModel::setRoles(const QStringList &roles) 0312 { 0313 m_roles = roles; 0314 } 0315 0316 int InstallerListModel::rowCount(const QModelIndex &) const 0317 { 0318 return m_jsonList.size(); 0319 } 0320 0321 QHash<int, QByteArray> InstallerListModel::roleNames() const 0322 { 0323 QHash<int, QByteArray> ret; 0324 for ( int i = 0; i < m_roles.size(); i++ ) { 0325 ret.insert(i, m_roles[i].toUtf8()); 0326 } 0327 return ret; 0328 } 0329 0330 QVariant InstallerListModel::data(const QModelIndex &index, int role) const 0331 { 0332 return m_jsonList[index.row()][role]; 0333 } 0334 0335 QVariantMap InstallerListModel::get(int row) 0336 { 0337 QHash<int,QByteArray> names = roleNames(); 0338 QHashIterator<int, QByteArray> i(names); 0339 QVariantMap res; 0340 QModelIndex idx = index(row, 0); 0341 while (i.hasNext()) { 0342 i.next(); 0343 QVariant data = idx.data(i.key()); 0344 res[i.value()] = data; 0345 } 0346 return res; 0347 } 0348 0349 bool InstallerListModel::downloadingModel() 0350 { 0351 return m_downloadingModel; 0352 } 0353 0354 void InstallerListModel::setDownloadingModel(bool downloadingModel) 0355 { 0356 m_downloadingModel = downloadingModel; 0357 emit downloadingModelUpdated(); 0358 } 0359 0360 bool InstallerListModel::creatingModel() 0361 { 0362 return m_creatingModel; 0363 } 0364 0365 void InstallerListModel::setCreatingModel(bool creatingModel) 0366 { 0367 m_creatingModel = creatingModel; 0368 emit creatingModelUpdated(); 0369 } 0370 0371 int InstallerListModel::completeModelCounter() 0372 { 0373 return m_originalCount; 0374 } 0375 0376 int InstallerListModel::updatingModelCounter() 0377 { 0378 return m_updatingCount; 0379 } 0380 0381 void InstallerListModel::setCategoryBrowser(int category) 0382 { 0383 switch (category) { 0384 case 0: 0385 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=608&pagesize=100&format=json"; 0386 m_cacheDataFile = "/general.json" ; 0387 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0388 break; 0389 case 1: 0390 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=609&pagesize=100&format=json"; 0391 m_cacheDataFile = "/configuration.json"; 0392 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0393 break; 0394 case 2: 0395 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=415&pagesize=100&format=json"; 0396 m_cacheDataFile = "/entertainment.json"; 0397 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0398 break; 0399 case 3: 0400 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=610&pagesize=100&format=json"; 0401 m_cacheDataFile = "/information.json"; 0402 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0403 break; 0404 case 4: 0405 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=611&pagesize=100&format=json"; 0406 m_cacheDataFile = "/productivity.json"; 0407 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0408 break; 0409 default: 0410 m_categoryUrl = "https://api.kde-look.org/ocs/v1/content/data?categories=608&pagesize=100&format=json"; 0411 m_cacheDataFile = "/general.json"; 0412 m_cacheDataPath = "/opt/MycroftSkillInstaller/cache/" + m_cacheDataFile; 0413 break; 0414 } 0415 } 0416 0417 QString InstallerListModel::getCurrentCategory() 0418 { 0419 return m_categoryUrl; 0420 } 0421 0422 void InstallerListModel::fetchLatestForCurrentModel() 0423 { 0424 QFile file(m_cacheDataPath); 0425 file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); 0426 file.write(""); 0427 file.close(); 0428 reloadJsonModel(); 0429 }