#region ================== Namespaces using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net; using System.Text; using System.Windows.Forms; using System.Xml; using SharpCompress.Archive.SevenZip; using SharpCompress.Common; using SharpCompress.Reader; #endregion namespace CodeImp.DoomBuilder { internal static class UpdateChecker { private delegate DialogResult ShowWarningMessageDelegate(string message, MessageBoxButtons buttons); private const string NO_UPDATE_REQUIRED = "Your version is up to date."; private static BackgroundWorker worker; private static bool verbose; internal static void PerformCheck(bool verbosemode) { // Update check already runing? if(worker != null && worker.IsBusy) { if(verbosemode) General.ShowWarningMessage("Update check is already running!", MessageBoxButtons.OK); return; } // Check if we have write access... if(!General.CheckWritePremissions(General.AppPath)) { string msg = "Cannot perform update: your user account does not have write access to the destination folder \"" + General.AppPath + "\"" + "Move the editor to a folder with write access, or run it as Administrator."; if(verbosemode) General.ShowWarningMessage(msg, MessageBoxButtons.OK); else General.ErrorLogger.Add(ErrorType.Error, msg); return; } // Start checking verbose = verbosemode; worker = new BackgroundWorker(); worker.DoWork += DoWork; worker.RunWorkerCompleted += RunWorkerCompleted; worker.WorkerSupportsCancellation = true; worker.RunWorkerAsync(); } private static void DoWork(object sender, DoWorkEventArgs e) { string updaterpath = Path.Combine(General.AppPath, "Updater.exe"); if(!File.Exists(updaterpath)) { ShowResult("Update check failed: \"" + updaterpath + "\" does not exist!"); e.Cancel = true; return; } string inipath = Path.Combine(General.AppPath, "Updater.ini"); if(!File.Exists(inipath)) { ShowResult("Update check failed: \"" + inipath + "\" does not exist!"); e.Cancel = true; return; } // Get some ini values... string url = string.Empty; string updaterpackname = string.Empty; string[] inilines = File.ReadAllLines(inipath); foreach(string line in inilines) { if(line.StartsWith("URL")) url = line.Substring(3).Trim(); else if(line.StartsWith("UpdaterName")) updaterpackname = line.Substring(11).Trim(); } if(string.IsNullOrEmpty(url)) { ShowResult("Update check failed: failed to get update url from Updater.ini!"); e.Cancel = true; return; } if(string.IsNullOrEmpty(updaterpackname)) { ShowResult("Update check failed: failed to get updater pack name from Updater.ini!"); e.Cancel = true; return; } // Get local revision number int localrev = General.ThisAssembly.GetName().Version.Revision; int actuallocalrev = localrev; if(!verbose) localrev = Math.Max(localrev, General.Settings.IgnoredRemoteRevision); // Get remote revision numbers int remoterev, remoteupdaterrev; MemoryStream stream = DownloadWebFile(Path.Combine(url, "Versions.txt")); if(stream == null) { ShowResult("Update check failed: failed to retrieve remote revision info.\nCheck your Internet connection and try again."); e.Cancel = true; return; } List lines = new List(2); using(StreamReader reader = new StreamReader(stream)) { while(!reader.EndOfStream) lines.Add(reader.ReadLine()); } if(lines.Count != 2) { ShowResult("Update check failed: invalid remote revision number."); e.Cancel = true; return; } if(!int.TryParse(lines[0], out remoterev)) { ShowResult("Update check failed: failed to retrieve remote revision number."); e.Cancel = true; return; } if(!int.TryParse(lines[1], out remoteupdaterrev)) { ShowResult("Update check failed: failed to retrieve remote updater revision number."); e.Cancel = true; return; } // Update the updater! string result = UpdateUpdater(url, updaterpackname, remoteupdaterrev); if(!string.IsNullOrEmpty(result)) { ShowResult("Update check failed: " + result); e.Cancel = true; return; } if(remoterev > localrev) { // Get changelog info string changelog = GetChangelog(url, actuallocalrev); if(string.IsNullOrEmpty(changelog)) { ShowResult("Update check failed: failed to retrieve changelog.\nCheck your Internet connection and try again."); e.Cancel = true; return; } // Pass data to MainForm General.MainWindow.UpdateAvailable(remoterev, changelog); } else if(verbose) { ShowResult(NO_UPDATE_REQUIRED); } } private static string UpdateUpdater(string url, string updaterpackname, int remoterev) { // Check if updater is running... try { Process[] processes = Process.GetProcesses(); // Abort if it's running... foreach(Process process in processes) { if(process.ProcessName == "Updater" && Path.GetDirectoryName(process.MainModule.FileName) == Application.StartupPath) { return "Updater.exe is already running."; } } } catch(Exception e) { return "failed to check Updater process: " + e.Message; } // Check local revision int localrev = FileVersionInfo.GetVersionInfo("Updater.exe").ProductPrivatePart; if(localrev < remoterev) { // Download update MemoryStream stream = DownloadWebFile(Path.Combine(url, updaterpackname)); if(stream == null) { return "failed to download Updater package."; } // Unpack update try { using(SevenZipArchive arc = SevenZipArchive.Open(stream)) { if(!arc.IsComplete) return "downloaded Updater package is not complete."; IReader reader = arc.ExtractAllEntries(); // Unpack all while(reader.MoveToNextEntry()) { if(reader.Entry.IsDirectory) continue; // Shouldn't be there, but who knows... reader.WriteEntryToDirectory(General.AppPath, ExtractOptions.ExtractFullPath | ExtractOptions.Overwrite); } } } catch(Exception e) { return "failed to unpack the Updater: " + e.Message; } } return string.Empty; } private static void ShowResult(string message) { if(!string.IsNullOrEmpty(message)) { if(verbose) { if(General.MainWindow.InvokeRequired) General.MainWindow.Invoke(new ShowWarningMessageDelegate(General.ShowWarningMessage), new object[] { message, MessageBoxButtons.OK }); else General.ShowWarningMessage(message, MessageBoxButtons.OK); } else if(message != NO_UPDATE_REQUIRED) { General.ErrorLogger.Add(ErrorType.Error, message); } } } private static void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { worker = null; } private static string GetChangelog(string url, int localrev) { StringBuilder sb = new StringBuilder(@"{\rtf1"); using(MemoryStream stream = DownloadWebFile(Path.Combine(url, "Changelog.xml"))) { if(stream == null) return string.Empty; XmlDocument doc = new XmlDocument(); doc.Load(stream); // Revision infos go in descending order if(doc.ChildNodes.Count == 0) return string.Empty; foreach(XmlNode log in doc.ChildNodes) { if(log.ChildNodes.Count == 0) continue; foreach(XmlNode logentry in log.ChildNodes) { int noderev; if(logentry.Attributes == null || !int.TryParse(logentry.Attributes.GetNamedItem("revision").Value, out noderev)) continue; if(noderev <= localrev) break; // Add info sb.Append(@"{\b R" + noderev + @":}\par "); foreach(XmlNode prop in logentry.ChildNodes) { if(prop.Name == "msg") { sb.Append(prop.InnerText.Trim().Replace(Environment.NewLine, @"\par ")).Append(@"\par\par "); break; } } } } } sb.Append("}"); return sb.ToString(); } private static MemoryStream DownloadWebFile(string url) { // Open a data stream from the supplied URL WebRequest request = WebRequest.Create(url); WebResponse response; try { response = request.GetResponse(); } catch(WebException) { return null; } Stream source = response.GetResponseStream(); if(source == null) return null; // Download the data in chuncks byte[] buffer = new byte[1024]; // Download the data MemoryStream result = new MemoryStream(); while(!General.MainWindow.IsDisposed) { // Let's try and read the data int numbytes = source.Read(buffer, 0, buffer.Length); if(numbytes == 0) break; // Download complete // Write the downloaded data result.Write(buffer, 0, numbytes); } // Release resources source.Close(); // Rewind and return the stream result.Position = 0; return result; } } }