Преглед изворни кода

Move update endpoint parsing to host from web-client.

This allows the updater to run on whatever web-client is currently
loaded.

Fixes plexinc/plex-media-player-private#521
Tobias Hieta пре 8 година
родитељ
комит
640ba62604

+ 1 - 1
src/main.cpp

@@ -112,7 +112,7 @@ int main(int argc, char *argv[])
 
     preinitQt();
 
-    QGuiApplication app(argc, newArgv);
+    QApplication app(argc, newArgv);
     app.setWindowIcon(QIcon(":/images/icon.png"));
 
     // Get the arguments from the app, this is the parsed version of newArgc and newArgv

+ 1 - 0
src/system/SystemComponent.cpp

@@ -281,6 +281,7 @@ void SystemComponent::userInformation(const QVariantMap& userModel)
   SettingsComponent::Get().setUserRoleList(roleList);
 
   m_authenticationToken = userModel.value("authToken").toString();
+  emit userInfoChanged();
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////

+ 1 - 0
src/system/SystemComponent.h

@@ -47,6 +47,7 @@ public:
 
   Q_INVOKABLE QString getCapabilitiesString();
   Q_SIGNAL void capabilitiesChanged(const QString& capabilities);
+  Q_SIGNAL void userInfoChanged();
 
   // possible os types type enum
   enum PlatformType

+ 189 - 18
src/system/UpdaterComponent.cpp

@@ -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;
+}
+
 

+ 15 - 1
src/system/UpdaterComponent.h

@@ -141,9 +141,15 @@ public:
   const char* componentName() override { return "updater"; }
   bool componentInitialize() override { return true; }
 
-  Q_INVOKABLE void downloadUpdate(const QVariantMap &updateInfo);
+  // Disable old API for now
+  Q_INVOKABLE void downloadUpdate(const QVariantMap &updateInfo) { };
+
+  Q_INVOKABLE void checkForUpdate();
+  Q_INVOKABLE void startUpdateDownload(const QVariantHash& updateInfo);
+
   Q_INVOKABLE void doUpdate();
 
+  const QVariantHash& updateInfo() const { return m_updateInfo; }
 
 signals:
   void downloadError(const QString& error);
@@ -167,6 +173,14 @@ private:
   bool m_hasManifest;
 
   QNetworkAccessManager m_netManager;
+  QNetworkReply* m_checkReply;
+  QByteArray m_checkData;
+
+  QVariantHash parseUpdateData(const QByteArray& data);
+  QString getFinalUrl(const QString& path);
+
+  QVariantHash m_updateInfo;
+  QTime m_lastUpdateCheck;
 };
 
 #endif // UPDATERCOMPONENT_H

+ 60 - 1
src/ui/KonvergoWindow.cpp

@@ -4,7 +4,11 @@
 #include <QScreen>
 #include <QQuickItem>
 #include <QGuiApplication>
+#include <QMessageBox>
+#include <QPushButton>
 
+#include "core/Version.h"
+#include "system/UpdaterComponent.h"
 #include "input/InputKeyboard.h"
 #include "settings/SettingsComponent.h"
 #include "settings/SettingsSection.h"
@@ -18,7 +22,12 @@
 #include "EventFilter.h"
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-KonvergoWindow::KonvergoWindow(QWindow* parent) : QQuickWindow(parent), m_debugLayer(false), m_lastScale(1.0), m_ignoreFullscreenSettingsChange(0)
+KonvergoWindow::KonvergoWindow(QWindow* parent) :
+  QQuickWindow(parent),
+  m_debugLayer(false),
+  m_lastScale(1.0),
+  m_ignoreFullscreenSettingsChange(0),
+  m_showedUpdateDialog(false)
 {
   // NSWindowCollectionBehaviorFullScreenPrimary is only set on OSX if Qt::WindowFullscreenButtonHint is set on the window.
   setFlags(flags() | Qt::WindowFullscreenButtonHint);
@@ -65,6 +74,9 @@ KonvergoWindow::KonvergoWindow(QWindow* parent) : QQuickWindow(parent), m_debugL
 
   connect(qApp, &QCoreApplication::aboutToQuit, this, &KonvergoWindow::closingWindow);
 
+  connect(&UpdaterComponent::Get(), &UpdaterComponent::downloadComplete,
+          this, &KonvergoWindow::showUpdateDialog);
+
 #ifdef KONVERGO_OPENELEC
   setVisibility(QWindow::FullScreen);
 #else
@@ -74,6 +86,53 @@ KonvergoWindow::KonvergoWindow(QWindow* parent) : QQuickWindow(parent), m_debugL
   emit enableVideoWindowSignal();
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
+void KonvergoWindow::showUpdateDialog()
+{
+  if (m_webDesktopMode && !m_showedUpdateDialog)
+  {
+    QVariantHash updateInfo = UpdaterComponent::Get().updateInfo();
+
+    QString currentVersion = Version::GetCanonicalVersionString().split("-")[0];
+    QString newVersion = updateInfo["version"].toString().split("-")[0];
+
+    QMessageBox* message = new QMessageBox(nullptr);
+    message->setIcon(QMessageBox::Information);
+    message->setWindowModality(Qt::ApplicationModal);
+    message->setWindowTitle("Update found!");
+    message->setText("An update to Plex Media Player was found");
+    auto infoText = QString("You are currently running version %0\nDo you wish to install version %1 now?")
+      .arg(currentVersion)
+      .arg(newVersion);
+    message->setInformativeText(infoText);
+
+    auto details = QString("ChangeLog for version %0\n\nNew:\n%1\n\nFixed:\n%2")
+      .arg(newVersion)
+      .arg(updateInfo["new"].toString())
+      .arg(updateInfo["fixed"].toString());
+
+    message->setDetailedText(details);
+
+    auto updateNow = message->addButton("Install Now", QMessageBox::AcceptRole);
+    auto updateLater = message->addButton("Install on Next Restart", QMessageBox::RejectRole);
+
+    message->setDefaultButton(updateNow);
+
+    m_showedUpdateDialog = true;
+    connect(message, &QMessageBox::buttonClicked, [=](QAbstractButton* button)
+    {
+      if (button == updateNow)
+        UpdaterComponent::Get().doUpdate();
+      else if (button == updateLater)
+        message->close();
+
+      message->deleteLater();
+    });
+
+    message->show();
+  }
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 void KonvergoWindow::closingWindow()
 {

+ 2 - 0
src/ui/KonvergoWindow.h

@@ -117,6 +117,7 @@ private slots:
   void onScreenCountChanged(int newCount);
   void updateDebugInfo();
   void playerWindowVisible(bool visible);
+  void showUpdateDialog();
 
 private:
   void notifyScale(const QSize& size);
@@ -132,6 +133,7 @@ private:
   QString m_debugInfo, m_systemDebugInfo, m_videoInfo;
   int m_ignoreFullscreenSettingsChange;
   bool m_webDesktopMode;
+  bool m_showedUpdateDialog;
 };
 
 #endif // KONVERGOWINDOW_H