|
@@ -14,42 +14,106 @@
|
|
|
#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)
|
|
|
+UpdaterComponent::UpdaterComponent(QObject* parent) :
|
|
|
+ ComponentBase(parent),
|
|
|
+ m_netManager(this),
|
|
|
+ m_checkReply(nullptr)
|
|
|
{
|
|
|
m_file = nullptr;
|
|
|
m_manifest = nullptr;
|
|
|
|
|
|
- connect(&m_netManager, &QNetworkAccessManager::finished,
|
|
|
- this, &UpdaterComponent::dlComplete);
|
|
|
+ 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()
|
|
|
+{
|
|
|
+ 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("distro", 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)
|
|
|
{
|
|
|
- QLOG_DEBUG() << "Hello, dlComplete";
|
|
|
if (reply->error() == QNetworkReply::NoError)
|
|
|
{
|
|
|
QUrl redirectURL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
|
|
- QLOG_DEBUG() << "Redirect:" << redirectURL.toString();
|
|
|
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://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);
|
|
|
- //connect(redirReply, &QNetworkReply::downloadProgress, this, &UpdaterComponent::downloadProgress);
|
|
|
- }
|
|
|
|
|
|
QLOG_DEBUG() << "Redirecting to:" << redirectURL.toString();
|
|
|
|
|
@@ -63,7 +127,9 @@ void UpdaterComponent::dlComplete(QNetworkReply* reply)
|
|
|
emit downloadError(reply->errorString());
|
|
|
|
|
|
if (m_hasManifest)
|
|
|
+ {
|
|
|
m_manifest->abort();
|
|
|
+ }
|
|
|
|
|
|
m_file->abort();
|
|
|
}
|
|
@@ -77,8 +143,7 @@ void UpdaterComponent::downloadFile(Update* update)
|
|
|
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;
|
|
|
+ else QLOG_ERROR() << "Failed to start download:" << update->m_url;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -119,21 +184,21 @@ bool UpdaterComponent::fileComplete(Update* update)
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
-void UpdaterComponent::downloadUpdate(const QVariantMap& updateInfo)
|
|
|
+void UpdaterComponent::startUpdateDownload(const QVariantHash& updateInfo)
|
|
|
{
|
|
|
if (isDownloading())
|
|
|
+ {
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
- QLOG_INFO() << updateInfo;
|
|
|
-
|
|
|
- if (!updateInfo.contains("version") ||
|
|
|
- !updateInfo.contains("manifestURL") || !updateInfo.contains("manifestHash") ||
|
|
|
+ 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(),
|
|
@@ -147,9 +212,10 @@ void UpdaterComponent::downloadUpdate(const QVariantMap& updateInfo)
|
|
|
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);
|
|
|
|
|
@@ -168,20 +234,26 @@ void UpdaterComponent::downloadUpdate(const QVariantMap& updateInfo)
|
|
|
// 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());
|
|
|
+ (m_file && m_file->m_reply && m_file->m_reply->isRunning());
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -192,4 +264,103 @@ void UpdaterComponent::doUpdate()
|
|
|
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("manifest");
|
|
|
+ package["fileHash"] = packageElement.attribute("manifestHash");
|
|
|
+
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
|