123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- #include "UpdaterComponent.h"
- #include "QsLog.h"
- #include "utils/Utils.h"
- #include <QTimer>
- #include <QCoreApplication>
- #include <QStandardPaths>
- #include <QCryptographicHash>
- #include <QIODevice>
- #include <QNetworkReply>
- #include <QNetworkAccessManager>
- #include <QDir>
- #include <QFile>
- #include <QProcess>
- #include <QStringList>
- #include <utils/HelperLauncher.h>
- #include <QUrlQuery>
- #include <QDomDocument>
- #include "qhttpclient.hpp"
- #include "qhttpclientresponse.hpp"
- #include "settings/SettingsComponent.h"
- #include "UpdateManager.h"
- #include "SystemComponent.h"
- using namespace qhttp::client;
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- UpdaterComponent::UpdaterComponent(QObject* parent) :
- ComponentBase(parent),
- m_netManager(this),
- m_checkReply(nullptr),
- m_enabled(true)
- {
- m_file = nullptr;
- m_manifest = nullptr;
- #ifndef NDEBUG
- m_enabled = false;
- #endif
- connect(&m_netManager, &QNetworkAccessManager::finished, this, &UpdaterComponent::dlComplete);
- connect(&SystemComponent::Get(), &SystemComponent::userInfoChanged, [&](){
- QTimer::singleShot(10 * 1000, this, &UpdaterComponent::checkForUpdate);
- });
- auto updateTimer = new QTimer(this);
- connect(updateTimer, &QTimer::timeout, [&]{
- auto diff = m_lastUpdateCheck.secsTo(QTime::currentTime());
- QLOG_DEBUG() << "It has gone" << diff << "seconds since last update check.";
- if (diff >= (3 * 60 * 60))
- checkForUpdate();
- });
- updateTimer->start(5 * 60 * 1000);
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void UpdaterComponent::checkForUpdate()
- {
- if (!m_enabled)
- return;
- auto systemInfo = SystemComponent::Get().systemInformation();
- QUrl baseUrl = QString("https://plex.tv/updater/products/%0/check.xml").arg(systemInfo["productid"].toInt());
- QUrlQuery query;
- query.addQueryItem("version", systemInfo["version"].toString());
- // Use the next line for debugging.
- // query.addQueryItem("version", "1.1.5.405-43e1569b");
- query.addQueryItem("build", systemInfo["build"].toString());
- query.addQueryItem("distribution", systemInfo["dist"].toString());
- query.addQueryItem("channel", SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "updateChannel").toString());
- auto authToken = SystemComponent::Get().authenticationToken();
- if (!authToken.isEmpty())
- query.addQueryItem("X-Plex-Token", authToken);
- baseUrl.setQuery(query);
- if (!m_checkReply)
- {
- QLOG_DEBUG() << "Checking for updates at:" << baseUrl.toString();
- QNetworkRequest req(baseUrl);
- req.setPriority(QNetworkRequest::HighPriority);
- m_checkReply = m_netManager.get(req);
- connect(m_checkReply, &QNetworkReply::readyRead, [&]()
- {
- m_checkData.append(m_checkReply->read(m_checkReply->bytesAvailable()));
- });
- connect(m_checkReply, &QNetworkReply::finished, [&]()
- {
- auto updateData = parseUpdateData(m_checkData);
- if (!updateData.isEmpty())
- startUpdateDownload(updateData);
- m_checkData.clear();
- m_checkReply = nullptr;
- });
- }
- m_lastUpdateCheck = QTime::currentTime();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void UpdaterComponent::dlComplete(QNetworkReply* reply)
- {
- if (reply->error() == QNetworkReply::NoError)
- {
- QUrl redirectURL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
- if (!redirectURL.isEmpty())
- {
- QLOG_DEBUG() << "Redirect:" << redirectURL.toString();
- // redirection, check that we get redirected to something we expect.
- if (redirectURL.toString().startsWith("https://nightlies.plex.tv") ||
- redirectURL.toString().startsWith("https://downloads.plex.tv") ||
- redirectURL.toString().startsWith("https://plex.tv"))
- {
- QNetworkReply* redirReply = m_netManager.get(QNetworkRequest(redirectURL));
- if (m_manifest->m_reply == reply)
- m_manifest->setReply(redirReply);
- else if (m_file->m_reply == reply)
- m_file->setReply(redirReply);
- QLOG_DEBUG() << "Redirecting to:" << redirectURL.toString();
- return;
- }
- }
- }
- else
- {
- QLOG_ERROR() << "Error downloading:" << reply->url() << "-" << reply->errorString();
- emit downloadError(reply->errorString());
- if (m_hasManifest && m_manifest)
- {
- m_manifest->abort();
- }
- if (m_file)
- m_file->abort();
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void UpdaterComponent::downloadFile(Update* update)
- {
- QNetworkRequest request(update->m_url);
- request.setPriority(QNetworkRequest::LowPriority);
- request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, true);
- if (update->setReply(m_netManager.get(request)))
- QLOG_INFO() << "Downloading update:" << update->m_url << "to:" << update->m_localPath;
- else QLOG_ERROR() << "Failed to start download:" << update->m_url;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- bool UpdaterComponent::fileComplete(Update* update)
- {
- Q_UNUSED(update);
- if (m_file->isReady() && (m_manifest->isReady() || !m_hasManifest))
- {
- QLOG_DEBUG() << "Both files downloaded";
- // create a file that shows that we are ready
- // to apply this update
- //
- QFile readyFile(UpdateManager::GetPath("_readyToApply", m_version, false));
- if (readyFile.open(QFile::WriteOnly))
- {
- readyFile.write("FOO");
- readyFile.close();
- }
- emit downloadComplete(m_version);
- delete m_file;
- delete m_manifest;
- m_file = nullptr;
- m_manifest = nullptr;
- return true;
- }
- if (update)
- {
- QLOG_DEBUG() << "File " << update->m_localPath << " download completed";
- }
- return false;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void UpdaterComponent::startUpdateDownload(const QVariantHash& updateInfo)
- {
- if (isDownloading())
- {
- return;
- }
- if (!updateInfo.contains("version") || !updateInfo.contains("manifestURL") || !updateInfo.contains("manifestHash") ||
- !updateInfo.contains("fileURL") || !updateInfo.contains("fileHash") || !updateInfo.contains("fileName"))
- {
- QLOG_ERROR() << "updateInfo was missing fields required to carry out this action.";
- return;
- }
- m_updateInfo = updateInfo;
- m_version = updateInfo["version"].toString();
- m_manifest = new Update(updateInfo["manifestURL"].toString(),
- UpdateManager::GetPath("manifest.xml.bz2", m_version, false),
- updateInfo["manifestHash"].toString(), this);
- // determine if we have a manifest (some distros don't like OE)
- m_hasManifest = ((!m_manifest->m_url.isEmpty()) && (!m_manifest->m_hash.isEmpty()));
- m_file = new Update(updateInfo["fileURL"].toString(),
- UpdateManager::GetPath(updateInfo["fileName"].toString(), m_version, true),
- updateInfo["fileHash"].toString(), this);
- if (m_hasManifest)
- {
- connect(m_manifest, &Update::fileDone, this, &UpdaterComponent::fileComplete);
- }
- connect(m_file, &Update::fileDone, this, &UpdaterComponent::fileComplete);
- // create directories we need
- QDir dr(QFileInfo(m_file->m_localPath).dir());
- if (!dr.exists())
- {
- if (!dr.mkpath("."))
- {
- QLOG_ERROR() << "Failed to create update directory:" << dr.absolutePath();
- emit downloadError("Failed to create download directory");
- return;
- }
- }
- // this will first check if the files are done
- // and in that case emit the done signal.
- if (fileComplete(nullptr))
- {
- return;
- }
- if (!m_manifest->isReady() && m_hasManifest)
- {
- downloadFile(m_manifest);
- }
- if (!m_file->isReady())
- {
- downloadFile(m_file);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- bool UpdaterComponent::isDownloading()
- {
- return (m_manifest && m_manifest->m_reply && m_manifest->m_reply->isRunning()) ||
- (m_file && m_file->m_reply && m_file->m_reply->isRunning());
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void UpdaterComponent::doUpdate()
- {
- // make sure we kill off the helper first:
- HelperLauncher::Get().stop();
- UpdateManager::Get()->doUpdate(m_version);
- }
- #define BASE_PLEX_TV_URL "https://plex.tv"
- /////////////////////////////////////////////////////////////////////////////////////////
- QString UpdaterComponent::getFinalUrl(const QString& path)
- {
- QUrl baseUrl(BASE_PLEX_TV_URL);
- baseUrl.setPath(path);
- auto token = SystemComponent::Get().authenticationToken();
- if (!token.isEmpty())
- baseUrl.setQuery("X-Plex-Token=" + token);
- return baseUrl.toString();
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QVariantHash UpdaterComponent::parseUpdateData(const QByteArray& data)
- {
- QDomDocument doc("Update data");
- QVariantHash updateInfo;
- if (!doc.setContent(data))
- {
- QLOG_ERROR() << "Failed to parse check.xml data!";
- return updateInfo;
- }
- QVariantList releases;
- auto root = doc.documentElement();
- auto releaseElement = root.firstChildElement();
- while (!releaseElement.isNull())
- {
- if (releaseElement.nodeName() == "Release")
- {
- QVariantHash rel;
- rel["version"] = releaseElement.attribute("version");
- rel["fixed"] = releaseElement.attribute("fixed", "Nothing");
- rel["new"] = releaseElement.attribute("new", "Nothing");
- QVariantList packages;
- auto packageElement = releaseElement.firstChildElement();
- while (!packageElement.isNull())
- {
- if (packageElement.nodeName() == "Package")
- {
- QVariantHash package;
- package["delta"] = packageElement.attribute("delta");
- package["manifest"] = packageElement.attribute("manifest");
- package["manifestHash"] = packageElement.attribute("manifestHash");
- package["file"] = packageElement.attribute("file");
- package["fileHash"] = packageElement.attribute("fileHash");
- package["fileName"] = packageElement.attribute("fileName");
- if (package["delta"].toString() == "true")
- rel["delta_package"] = package;
- else
- rel["full_package"] = package;
- }
- packageElement = packageElement.nextSiblingElement();
- }
- releases.append(rel);
- }
- releaseElement = releaseElement.nextSiblingElement();
- }
- if (releases.isEmpty())
- {
- QLOG_DEBUG() << "No updates found!";
- return updateInfo;
- }
- QLOG_DEBUG() << releases;
- auto release = releases.last().toHash();
- if (release.contains("delta_package"))
- updateInfo = release["delta_package"].toHash();
- else
- updateInfo = release["full_package"].toHash();
- updateInfo["version"] = release["version"];
- updateInfo["fixed"] = release["fixed"];
- updateInfo["new"] = release["new"];
- updateInfo["fileURL"] = getFinalUrl(updateInfo["file"].toString());
- updateInfo["manifestURL"] = getFinalUrl(updateInfo["manifest"].toString());
- QLOG_DEBUG() << updateInfo;
- return updateInfo;
- }
|