UpdaterComponent.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. #include "UpdaterComponent.h"
  2. #include "QsLog.h"
  3. #include "utils/Utils.h"
  4. #include <QTimer>
  5. #include <QCoreApplication>
  6. #include <QStandardPaths>
  7. #include <QCryptographicHash>
  8. #include <QIODevice>
  9. #include <QNetworkReply>
  10. #include <QNetworkAccessManager>
  11. #include <QDir>
  12. #include <QFile>
  13. #include <QProcess>
  14. #include <QStringList>
  15. #include <utils/HelperLauncher.h>
  16. #include <QUrlQuery>
  17. #include <QDomDocument>
  18. #include "qhttpclient.hpp"
  19. #include "qhttpclientresponse.hpp"
  20. #include "settings/SettingsComponent.h"
  21. #include "UpdateManager.h"
  22. #include "SystemComponent.h"
  23. using namespace qhttp::client;
  24. ///////////////////////////////////////////////////////////////////////////////////////////////////
  25. UpdaterComponent::UpdaterComponent(QObject* parent) :
  26. ComponentBase(parent),
  27. m_netManager(this),
  28. m_checkReply(nullptr),
  29. m_enabled(true)
  30. {
  31. m_file = nullptr;
  32. m_manifest = nullptr;
  33. #ifndef NDEBUG
  34. m_enabled = false;
  35. #endif
  36. connect(&m_netManager, &QNetworkAccessManager::finished, this, &UpdaterComponent::dlComplete);
  37. connect(&SystemComponent::Get(), &SystemComponent::userInfoChanged, [&](){
  38. QTimer::singleShot(10 * 1000, this, &UpdaterComponent::checkForUpdate);
  39. });
  40. auto updateTimer = new QTimer(this);
  41. connect(updateTimer, &QTimer::timeout, [&]{
  42. auto diff = m_lastUpdateCheck.secsTo(QTime::currentTime());
  43. QLOG_DEBUG() << "It has gone" << diff << "seconds since last update check.";
  44. if (diff >= (3 * 60 * 60))
  45. checkForUpdate();
  46. });
  47. updateTimer->start(5 * 60 * 1000);
  48. }
  49. /////////////////////////////////////////////////////////////////////////////////////////
  50. void UpdaterComponent::checkForUpdate()
  51. {
  52. if (!m_enabled)
  53. return;
  54. auto systemInfo = SystemComponent::Get().systemInformation();
  55. QUrl baseUrl = QString("https://plex.tv/updater/products/%0/check.xml").arg(systemInfo["productid"].toInt());
  56. QUrlQuery query;
  57. query.addQueryItem("version", systemInfo["version"].toString());
  58. // Use the next line for debugging.
  59. // query.addQueryItem("version", "1.1.5.405-43e1569b");
  60. query.addQueryItem("build", systemInfo["build"].toString());
  61. query.addQueryItem("distribution", systemInfo["dist"].toString());
  62. query.addQueryItem("channel", SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "updateChannel").toString());
  63. auto authToken = SystemComponent::Get().authenticationToken();
  64. if (!authToken.isEmpty())
  65. query.addQueryItem("X-Plex-Token", authToken);
  66. baseUrl.setQuery(query);
  67. if (!m_checkReply)
  68. {
  69. QLOG_DEBUG() << "Checking for updates at:" << baseUrl.toString();
  70. QNetworkRequest req(baseUrl);
  71. req.setPriority(QNetworkRequest::HighPriority);
  72. m_checkReply = m_netManager.get(req);
  73. connect(m_checkReply, &QNetworkReply::readyRead, [&]()
  74. {
  75. m_checkData.append(m_checkReply->read(m_checkReply->bytesAvailable()));
  76. });
  77. connect(m_checkReply, &QNetworkReply::finished, [&]()
  78. {
  79. auto updateData = parseUpdateData(m_checkData);
  80. if (!updateData.isEmpty())
  81. startUpdateDownload(updateData);
  82. m_checkData.clear();
  83. m_checkReply = nullptr;
  84. });
  85. }
  86. m_lastUpdateCheck = QTime::currentTime();
  87. }
  88. ///////////////////////////////////////////////////////////////////////////////////////////////////
  89. void UpdaterComponent::dlComplete(QNetworkReply* reply)
  90. {
  91. if (reply->error() == QNetworkReply::NoError)
  92. {
  93. QUrl redirectURL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
  94. if (!redirectURL.isEmpty())
  95. {
  96. QLOG_DEBUG() << "Redirect:" << redirectURL.toString();
  97. // redirection, check that we get redirected to something we expect.
  98. if (redirectURL.toString().startsWith("https://nightlies.plex.tv") ||
  99. redirectURL.toString().startsWith("https://downloads.plex.tv") ||
  100. redirectURL.toString().startsWith("https://plex.tv"))
  101. {
  102. QNetworkReply* redirReply = m_netManager.get(QNetworkRequest(redirectURL));
  103. if (m_manifest->m_reply == reply)
  104. m_manifest->setReply(redirReply);
  105. else if (m_file->m_reply == reply)
  106. m_file->setReply(redirReply);
  107. QLOG_DEBUG() << "Redirecting to:" << redirectURL.toString();
  108. return;
  109. }
  110. }
  111. }
  112. else
  113. {
  114. QLOG_ERROR() << "Error downloading:" << reply->url() << "-" << reply->errorString();
  115. emit downloadError(reply->errorString());
  116. if (m_hasManifest && m_manifest)
  117. {
  118. m_manifest->abort();
  119. }
  120. if (m_file)
  121. m_file->abort();
  122. }
  123. }
  124. ///////////////////////////////////////////////////////////////////////////////////////////////////
  125. void UpdaterComponent::downloadFile(Update* update)
  126. {
  127. QNetworkRequest request(update->m_url);
  128. request.setPriority(QNetworkRequest::LowPriority);
  129. request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, true);
  130. if (update->setReply(m_netManager.get(request)))
  131. QLOG_INFO() << "Downloading update:" << update->m_url << "to:" << update->m_localPath;
  132. else QLOG_ERROR() << "Failed to start download:" << update->m_url;
  133. }
  134. ///////////////////////////////////////////////////////////////////////////////////////////////////
  135. bool UpdaterComponent::fileComplete(Update* update)
  136. {
  137. Q_UNUSED(update);
  138. if (m_file->isReady() && (m_manifest->isReady() || !m_hasManifest))
  139. {
  140. QLOG_DEBUG() << "Both files downloaded";
  141. // create a file that shows that we are ready
  142. // to apply this update
  143. //
  144. QFile readyFile(UpdateManager::GetPath("_readyToApply", m_version, false));
  145. if (readyFile.open(QFile::WriteOnly))
  146. {
  147. readyFile.write("FOO");
  148. readyFile.close();
  149. }
  150. emit downloadComplete(m_version);
  151. delete m_file;
  152. delete m_manifest;
  153. m_file = nullptr;
  154. m_manifest = nullptr;
  155. return true;
  156. }
  157. if (update)
  158. {
  159. QLOG_DEBUG() << "File " << update->m_localPath << " download completed";
  160. }
  161. return false;
  162. }
  163. ///////////////////////////////////////////////////////////////////////////////////////////////////
  164. void UpdaterComponent::startUpdateDownload(const QVariantHash& updateInfo)
  165. {
  166. if (isDownloading())
  167. {
  168. return;
  169. }
  170. if (!updateInfo.contains("version") || !updateInfo.contains("manifestURL") || !updateInfo.contains("manifestHash") ||
  171. !updateInfo.contains("fileURL") || !updateInfo.contains("fileHash") || !updateInfo.contains("fileName"))
  172. {
  173. QLOG_ERROR() << "updateInfo was missing fields required to carry out this action.";
  174. return;
  175. }
  176. m_updateInfo = updateInfo;
  177. m_version = updateInfo["version"].toString();
  178. m_manifest = new Update(updateInfo["manifestURL"].toString(),
  179. UpdateManager::GetPath("manifest.xml.bz2", m_version, false),
  180. updateInfo["manifestHash"].toString(), this);
  181. // determine if we have a manifest (some distros don't like OE)
  182. m_hasManifest = ((!m_manifest->m_url.isEmpty()) && (!m_manifest->m_hash.isEmpty()));
  183. m_file = new Update(updateInfo["fileURL"].toString(),
  184. UpdateManager::GetPath(updateInfo["fileName"].toString(), m_version, true),
  185. updateInfo["fileHash"].toString(), this);
  186. if (m_hasManifest)
  187. {
  188. connect(m_manifest, &Update::fileDone, this, &UpdaterComponent::fileComplete);
  189. }
  190. connect(m_file, &Update::fileDone, this, &UpdaterComponent::fileComplete);
  191. // create directories we need
  192. QDir dr(QFileInfo(m_file->m_localPath).dir());
  193. if (!dr.exists())
  194. {
  195. if (!dr.mkpath("."))
  196. {
  197. QLOG_ERROR() << "Failed to create update directory:" << dr.absolutePath();
  198. emit downloadError("Failed to create download directory");
  199. return;
  200. }
  201. }
  202. // this will first check if the files are done
  203. // and in that case emit the done signal.
  204. if (fileComplete(nullptr))
  205. {
  206. return;
  207. }
  208. if (!m_manifest->isReady() && m_hasManifest)
  209. {
  210. downloadFile(m_manifest);
  211. }
  212. if (!m_file->isReady())
  213. {
  214. downloadFile(m_file);
  215. }
  216. }
  217. ///////////////////////////////////////////////////////////////////////////////////////////////////
  218. bool UpdaterComponent::isDownloading()
  219. {
  220. return (m_manifest && m_manifest->m_reply && m_manifest->m_reply->isRunning()) ||
  221. (m_file && m_file->m_reply && m_file->m_reply->isRunning());
  222. }
  223. ///////////////////////////////////////////////////////////////////////////////////////////////////
  224. void UpdaterComponent::doUpdate()
  225. {
  226. // make sure we kill off the helper first:
  227. HelperLauncher::Get().stop();
  228. UpdateManager::Get()->doUpdate(m_version);
  229. }
  230. #define BASE_PLEX_TV_URL "https://plex.tv"
  231. /////////////////////////////////////////////////////////////////////////////////////////
  232. QString UpdaterComponent::getFinalUrl(const QString& path)
  233. {
  234. QUrl baseUrl(BASE_PLEX_TV_URL);
  235. baseUrl.setPath(path);
  236. auto token = SystemComponent::Get().authenticationToken();
  237. if (!token.isEmpty())
  238. baseUrl.setQuery("X-Plex-Token=" + token);
  239. return baseUrl.toString();
  240. }
  241. /////////////////////////////////////////////////////////////////////////////////////////
  242. QVariantHash UpdaterComponent::parseUpdateData(const QByteArray& data)
  243. {
  244. QDomDocument doc("Update data");
  245. QVariantHash updateInfo;
  246. if (!doc.setContent(data))
  247. {
  248. QLOG_ERROR() << "Failed to parse check.xml data!";
  249. return updateInfo;
  250. }
  251. QVariantList releases;
  252. auto root = doc.documentElement();
  253. auto releaseElement = root.firstChildElement();
  254. while (!releaseElement.isNull())
  255. {
  256. if (releaseElement.nodeName() == "Release")
  257. {
  258. QVariantHash rel;
  259. rel["version"] = releaseElement.attribute("version");
  260. rel["fixed"] = releaseElement.attribute("fixed", "Nothing");
  261. rel["new"] = releaseElement.attribute("new", "Nothing");
  262. QVariantList packages;
  263. auto packageElement = releaseElement.firstChildElement();
  264. while (!packageElement.isNull())
  265. {
  266. if (packageElement.nodeName() == "Package")
  267. {
  268. QVariantHash package;
  269. package["delta"] = packageElement.attribute("delta");
  270. package["manifest"] = packageElement.attribute("manifest");
  271. package["manifestHash"] = packageElement.attribute("manifestHash");
  272. package["file"] = packageElement.attribute("file");
  273. package["fileHash"] = packageElement.attribute("fileHash");
  274. package["fileName"] = packageElement.attribute("fileName");
  275. if (package["delta"].toString() == "true")
  276. rel["delta_package"] = package;
  277. else
  278. rel["full_package"] = package;
  279. }
  280. packageElement = packageElement.nextSiblingElement();
  281. }
  282. releases.append(rel);
  283. }
  284. releaseElement = releaseElement.nextSiblingElement();
  285. }
  286. if (releases.isEmpty())
  287. {
  288. QLOG_DEBUG() << "No updates found!";
  289. return updateInfo;
  290. }
  291. QLOG_DEBUG() << releases;
  292. auto release = releases.last().toHash();
  293. if (release.contains("delta_package"))
  294. updateInfo = release["delta_package"].toHash();
  295. else
  296. updateInfo = release["full_package"].toHash();
  297. updateInfo["version"] = release["version"];
  298. updateInfo["fixed"] = release["fixed"];
  299. updateInfo["new"] = release["new"];
  300. updateInfo["fileURL"] = getFinalUrl(updateInfo["file"].toString());
  301. updateInfo["manifestURL"] = getFinalUrl(updateInfo["manifest"].toString());
  302. QLOG_DEBUG() << updateInfo;
  303. return updateInfo;
  304. }