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 }