PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/VidCoder/Services/Updater.cs

https://gitlab.com/playepic/vidcoder
C# | 494 lines | 400 code | 70 blank | 24 comment | 74 complexity | f46aca46f694985a15ae2b6fb614c439 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.SQLite;
  4. using System.Linq;
  5. using System.Text;
  6. using System.ComponentModel;
  7. using System.Xml.Linq;
  8. using System.IO;
  9. using System.Net;
  10. using System.Windows;
  11. using VidCoder.Model;
  12. using System.Diagnostics;
  13. namespace VidCoder.Services
  14. {
  15. using Resources;
  16. public class Updater : IUpdater
  17. {
  18. public const string UpdateInfoUrlBeta = "http://engy.us/VidCoder/latest-beta2.xml";
  19. public const string UpdateInfoUrlNonBeta = "http://engy.us/VidCoder/latest.xml";
  20. public event EventHandler<EventArgs<double>> UpdateDownloadProgress;
  21. public event EventHandler<EventArgs> UpdateStateChanged;
  22. private static bool DebugMode
  23. {
  24. get
  25. {
  26. #if DEBUG
  27. return true;
  28. #else
  29. return false;
  30. #endif
  31. }
  32. }
  33. private static bool Beta
  34. {
  35. get
  36. {
  37. #if BETA
  38. return true;
  39. #else
  40. return false;
  41. #endif
  42. }
  43. }
  44. private static bool BuildSupportsUpdates
  45. {
  46. get
  47. {
  48. return !DebugMode && !Utilities.IsPortable;
  49. }
  50. }
  51. private ILogger logger = Ioc.Container.GetInstance<ILogger>();
  52. private BackgroundWorker updateDownloader;
  53. private bool processDownloadsUpdates = true;
  54. private UpdateState state;
  55. public UpdateState State
  56. {
  57. get
  58. {
  59. return this.state;
  60. }
  61. set
  62. {
  63. if (value != this.state)
  64. {
  65. this.state = value;
  66. if (this.UpdateStateChanged != null)
  67. {
  68. this.UpdateStateChanged(this, new EventArgs());
  69. }
  70. }
  71. }
  72. }
  73. public string LatestVersion { get; set; }
  74. public void PromptToApplyUpdate()
  75. {
  76. if (BuildSupportsUpdates)
  77. {
  78. // If updates are enabled, and we are the last process instance, prompt to apply the update.
  79. if (Config.UpdatesEnabled && Utilities.CurrentProcessInstances == 1)
  80. {
  81. // See if the user has already applied the update manually
  82. string updateVersion = Config.UpdateVersion;
  83. if (!string.IsNullOrEmpty(updateVersion) && Utilities.CompareVersions(updateVersion, Utilities.CurrentVersion) <= 0)
  84. {
  85. // If we already have the newer version clear all the update info and cancel
  86. ClearUpdateMetadata();
  87. DeleteUpdatesFolder();
  88. return;
  89. }
  90. string installerPath = Config.UpdateInstallerLocation;
  91. if (installerPath != string.Empty && File.Exists(installerPath))
  92. {
  93. // An update is ready, to give a prompt to apply it.
  94. var updateConfirmation = new ApplyUpdateConfirmation();
  95. updateConfirmation.Owner = Ioc.Container.GetInstance<View.MainWindow>();
  96. updateConfirmation.ShowDialog();
  97. if (updateConfirmation.Result == "Yes")
  98. {
  99. this.ApplyUpdate();
  100. }
  101. else if (updateConfirmation.Result == "Disable")
  102. {
  103. Config.UpdatesEnabled = false;
  104. }
  105. }
  106. else
  107. {
  108. if (updateDownloader != null)
  109. {
  110. updateDownloader.CancelAsync();
  111. }
  112. }
  113. }
  114. }
  115. }
  116. public void ApplyUpdate()
  117. {
  118. // Re-check the process count in case another one was opened while the prompt was active.
  119. if (Utilities.CurrentProcessInstances == 1)
  120. {
  121. string installerPath = Config.UpdateInstallerLocation;
  122. Config.UpdateInProgress = true;
  123. var installerProcess = new Process();
  124. installerProcess.StartInfo = new ProcessStartInfo { FileName = installerPath, Arguments = "/silent /noicons /showSuccessDialog=\"yes\"" };
  125. installerProcess.Start();
  126. // Let the program close on its own.
  127. //Environment.Exit(0);
  128. }
  129. }
  130. public void HandleUpdatedSettings(bool updatesEnabled)
  131. {
  132. if (BuildSupportsUpdates)
  133. {
  134. if (updatesEnabled)
  135. {
  136. // If we don't already have an update waiting to install, check for updates.
  137. if (processDownloadsUpdates && Config.UpdateInstallerLocation == string.Empty)
  138. {
  139. this.StartBackgroundUpdate();
  140. }
  141. }
  142. else
  143. {
  144. if (updateDownloader != null)
  145. {
  146. // If we have just turned off updates, cancel any pending downloads.
  147. updateDownloader.CancelAsync();
  148. }
  149. }
  150. }
  151. }
  152. public bool HandlePendingUpdate()
  153. {
  154. // This flag signifies VidCoder is being run by the installer after an update.
  155. // In this case we report success, delete the installer, clean up the update flags and exit.
  156. bool updateInProgress = Config.UpdateInProgress;
  157. if (updateInProgress)
  158. {
  159. string targetUpdateVersion = Config.UpdateVersion;
  160. bool updateSucceeded = Utilities.CompareVersions(targetUpdateVersion, Utilities.CurrentVersion) == 0;
  161. using (SQLiteTransaction transaction = Database.Connection.BeginTransaction())
  162. {
  163. Config.UpdateInProgress = false;
  164. if (updateSucceeded)
  165. {
  166. ClearUpdateMetadata();
  167. }
  168. transaction.Commit();
  169. }
  170. if (updateSucceeded)
  171. {
  172. try
  173. {
  174. DeleteUpdatesFolder();
  175. }
  176. catch (IOException)
  177. {
  178. // Ignore this. Not critical that we delete the updates folder.
  179. }
  180. catch (UnauthorizedAccessException)
  181. {
  182. // Ignore this. Not critical that we delete the updates folder.
  183. }
  184. return true;
  185. }
  186. else
  187. {
  188. // If the target version is different from the currently running version,
  189. // this means the attempted upgrade failed. We give an error message but
  190. // continue with the program.
  191. MessageBox.Show(MainRes.UpdateNotAppliedError);
  192. }
  193. }
  194. return false;
  195. }
  196. private static string UpdateInfoUrl
  197. {
  198. get
  199. {
  200. #if BETA
  201. return UpdateInfoUrlBeta;
  202. #else
  203. return UpdateInfoUrlNonBeta;
  204. #endif
  205. }
  206. }
  207. private static void DeleteUpdatesFolder()
  208. {
  209. if (Directory.Exists(Utilities.UpdatesFolder))
  210. {
  211. Directory.Delete(Utilities.UpdatesFolder, true);
  212. }
  213. }
  214. private static void ClearUpdateMetadata()
  215. {
  216. Config.UpdateVersion = string.Empty;
  217. Config.UpdateInstallerLocation = string.Empty;
  218. Config.UpdateChangelogLocation = string.Empty;
  219. }
  220. // Starts checking for updates
  221. public void CheckUpdates()
  222. {
  223. // Only check for updates in release mode, non-portable
  224. if (!BuildSupportsUpdates)
  225. {
  226. return;
  227. }
  228. if (Utilities.CurrentProcessInstances > 1)
  229. {
  230. this.processDownloadsUpdates = false;
  231. return;
  232. }
  233. if (!Config.UpdatesEnabled)
  234. {
  235. // On a program restart, if updates are disabled, clean any pending installers.
  236. Config.UpdateInstallerLocation = string.Empty;
  237. if (Directory.Exists(Utilities.UpdatesFolder))
  238. {
  239. Directory.Delete(Utilities.UpdatesFolder, true);
  240. }
  241. return;
  242. }
  243. this.StartBackgroundUpdate();
  244. }
  245. private void StartBackgroundUpdate()
  246. {
  247. if (this.State != UpdateState.NotStarted && this.State != UpdateState.Failed && this.State != UpdateState.UpToDate)
  248. {
  249. // Can only start updates from certain states.
  250. return;
  251. }
  252. this.State = UpdateState.DownloadingInfo;
  253. this.updateDownloader = new BackgroundWorker { WorkerSupportsCancellation = true };
  254. this.updateDownloader.DoWork += CheckAndDownloadUpdate;
  255. this.updateDownloader.RunWorkerCompleted += (o, e) =>
  256. {
  257. };
  258. this.updateDownloader.RunWorkerAsync();
  259. }
  260. private void CheckAndDownloadUpdate(object sender, DoWorkEventArgs e)
  261. {
  262. var updateDownloader = sender as BackgroundWorker;
  263. SQLiteConnection connection = Database.ThreadLocalConnection;
  264. try
  265. {
  266. UpdateInfo updateInfo = GetUpdateInfo(Beta);
  267. if (updateInfo == null)
  268. {
  269. this.State = UpdateState.Failed;
  270. this.logger.Log("Update download failed. Unable to get update info.");
  271. return;
  272. }
  273. string updateVersion = updateInfo.LatestVersion;
  274. this.LatestVersion = updateVersion;
  275. if (Utilities.CompareVersions(updateVersion, Utilities.CurrentVersion) > 0)
  276. {
  277. // If an update is reported to be ready but the installer doesn't exist, clear out all the
  278. // installer info and redownload.
  279. string updateInstallerLocation = Config.UpdateInstallerLocation;
  280. if (updateInstallerLocation != string.Empty && !File.Exists(updateInstallerLocation))
  281. {
  282. using (SQLiteTransaction transaction = connection.BeginTransaction())
  283. {
  284. ClearUpdateMetadata();
  285. transaction.Commit();
  286. }
  287. this.logger.Log("Downloaded update (" + updateInstallerLocation + ") could not be found. Re-downloading it.");
  288. }
  289. // If we have not finished the download update yet, start/resume the download.
  290. if (Config.UpdateInstallerLocation == string.Empty)
  291. {
  292. string updateVersionText = updateVersion;
  293. #if BETA
  294. updateVersionText += " Beta";
  295. #endif
  296. string message = string.Format(MainRes.NewVersionDownloadStartedStatus, updateVersionText);
  297. this.logger.Log(message);
  298. this.logger.ShowStatus(message);
  299. this.State = UpdateState.DownloadingInstaller;
  300. string downloadLocation = updateInfo.DownloadLocation;
  301. string changelogLink = updateInfo.ChangelogLocation;
  302. string installerFileName = Path.GetFileName(downloadLocation);
  303. string installerFilePath = Path.Combine(Utilities.UpdatesFolder, installerFileName);
  304. Stream responseStream = null;
  305. FileStream fileStream = null;
  306. try
  307. {
  308. var request = (HttpWebRequest)WebRequest.Create(downloadLocation);
  309. int bytesProgressTotal = 0;
  310. if (File.Exists(installerFilePath))
  311. {
  312. var fileInfo = new FileInfo(installerFilePath);
  313. request.AddRange((int)fileInfo.Length);
  314. bytesProgressTotal = (int)fileInfo.Length;
  315. fileStream = new FileStream(installerFilePath, FileMode.Append, FileAccess.Write, FileShare.None);
  316. }
  317. else
  318. {
  319. fileStream = new FileStream(installerFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
  320. }
  321. var response = (HttpWebResponse)request.GetResponse();
  322. responseStream = response.GetResponseStream();
  323. byte[] downloadBuffer = new byte[2048];
  324. int bytesRead;
  325. while ((bytesRead = responseStream.Read(downloadBuffer, 0, downloadBuffer.Length)) > 0 && !updateDownloader.CancellationPending)
  326. {
  327. fileStream.Write(downloadBuffer, 0, bytesRead);
  328. bytesProgressTotal += bytesRead;
  329. if (this.UpdateDownloadProgress != null)
  330. {
  331. double completionPercentage = ((double)bytesProgressTotal * 100) / response.ContentLength;
  332. this.UpdateDownloadProgress(this, new EventArgs<double>(completionPercentage));
  333. }
  334. }
  335. if (bytesRead == 0)
  336. {
  337. using (SQLiteTransaction transaction = connection.BeginTransaction())
  338. {
  339. Config.UpdateVersion = updateVersion;
  340. Config.UpdateInstallerLocation = installerFilePath;
  341. Config.UpdateChangelogLocation = changelogLink;
  342. transaction.Commit();
  343. }
  344. this.State = UpdateState.InstallerReady;
  345. message = string.Format(MainRes.NewVersionDownloadFinishedStatus, updateVersionText);
  346. this.logger.Log(message);
  347. this.logger.ShowStatus(message);
  348. }
  349. else
  350. {
  351. // In this case the download must have been cancelled.
  352. this.State = UpdateState.NotStarted;
  353. }
  354. }
  355. finally
  356. {
  357. if (responseStream != null)
  358. {
  359. responseStream.Close();
  360. }
  361. if (fileStream != null)
  362. {
  363. fileStream.Close();
  364. }
  365. }
  366. }
  367. else
  368. {
  369. this.State = UpdateState.InstallerReady;
  370. }
  371. }
  372. else
  373. {
  374. this.State = UpdateState.UpToDate;
  375. }
  376. }
  377. catch (Exception exception)
  378. {
  379. this.State = UpdateState.Failed;
  380. this.logger.Log("Update download failed: " + exception.Message);
  381. }
  382. }
  383. internal static UpdateInfo GetUpdateInfo(bool beta)
  384. {
  385. string url = beta ? UpdateInfoUrlBeta : UpdateInfoUrlNonBeta;
  386. try
  387. {
  388. XDocument document = XDocument.Load(url);
  389. XElement root = document.Root;
  390. string configurationElementName;
  391. if (IntPtr.Size == 4)
  392. {
  393. configurationElementName = "Release";
  394. }
  395. else
  396. {
  397. configurationElementName = "Release-x64";
  398. }
  399. XElement configurationElement = root.Element(configurationElementName);
  400. if (configurationElement == null)
  401. {
  402. return null;
  403. }
  404. XElement latestElement = configurationElement.Element("Latest");
  405. XElement downloadElement = configurationElement.Element("DownloadLocation");
  406. XElement changelogLinkElement = configurationElement.Element("ChangelogLocation");
  407. if (latestElement == null || downloadElement == null || changelogLinkElement == null)
  408. {
  409. return null;
  410. }
  411. return new UpdateInfo
  412. {
  413. LatestVersion = latestElement.Value,
  414. DownloadLocation = downloadElement.Value,
  415. ChangelogLocation = changelogLinkElement.Value
  416. };
  417. }
  418. catch (WebException)
  419. {
  420. return null;
  421. }
  422. }
  423. }
  424. }