mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 04:12:12 +00:00
2c0f530ba5
Changed the way editor is closed during the update process (updater now asks to close the editor after downloading the update package). Added write access check before performing the update. The editor can now update the updater. Updater: merged relevant parts of SharpCompress into the updater source, reducing updater file size from 900 kb to 150 kb. Updated ZDoom_DECORATE.cfg.
483 lines
No EOL
13 KiB
C#
483 lines
No EOL
13 KiB
C#
#region ======================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Security.AccessControl;
|
|
using System.Security.Principal;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
using SharpCompress.Archives;
|
|
using SharpCompress.Readers;
|
|
|
|
#endregion
|
|
|
|
namespace mxd.GZDBUpdater
|
|
{
|
|
public partial class MainForm : Form
|
|
{
|
|
#region ======================== Variables
|
|
|
|
private string processToEnd = string.Empty;
|
|
private string downloadFile = string.Empty;
|
|
private const string revisionwildcard = "[REVNUM]";
|
|
private string URL = string.Empty;
|
|
private readonly string updateFolder = Application.StartupPath + @"\_update\";
|
|
private string appFileName = string.Empty;
|
|
private static BackgroundWorker worker;
|
|
private static bool appclosing;
|
|
private static MainForm me;
|
|
private const string MESSAGEBOX_TITLE = "Updater";
|
|
|
|
#endregion
|
|
|
|
#region ======================== Delegates
|
|
|
|
private delegate void SetLabelCallback(Label label, string text);
|
|
private delegate void UpdateProgressBarCallback(ByteArgs args, int step, int totalsteps);
|
|
private delegate void CloseDelegate();
|
|
|
|
#endregion
|
|
|
|
#region ======================== Properties
|
|
|
|
public static string ErrorDescription;
|
|
public static bool AppClosing { get { return appclosing; } }
|
|
|
|
#endregion
|
|
|
|
#region ======================== Constructor
|
|
|
|
public MainForm()
|
|
{
|
|
if(!CheckPremissions(Application.StartupPath))
|
|
{
|
|
ErrorDescription = "Update failed: your user account does not have write access to the destination folder \"" + Application.StartupPath + "\"\n\nMove the editor to a folder with write access,\nor run the updater as Administrator.";
|
|
InvokeClose();
|
|
}
|
|
else if(!File.Exists("Updater.ini"))
|
|
{
|
|
ErrorDescription = "Unable to locate 'Updater.ini'...";
|
|
InvokeClose();
|
|
}
|
|
else if(!LoadConfig("Updater.ini"))
|
|
{
|
|
InvokeClose();
|
|
}
|
|
else
|
|
{
|
|
me = this;
|
|
InitializeComponent();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ======================== Updater thread
|
|
|
|
private void BackgroundWorker(object sender, DoWorkEventArgs e)
|
|
{
|
|
UpdateLabel(label1, "1/6: Checking revisions...");
|
|
if(!UpdateRequired())
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
PreDownload();
|
|
|
|
UpdateLabel(label1, "2/6: Downloading Update...");
|
|
Webdata.BytesDownloaded += WebdataOnBytesDownloaded;
|
|
if(!Webdata.SaveWebFile(URL, downloadFile, updateFolder))
|
|
{
|
|
e.Cancel = true;
|
|
Webdata.BytesDownloaded -= WebdataOnBytesDownloaded;
|
|
return;
|
|
}
|
|
|
|
// Check if the editor is running...
|
|
if(!EditorClosed())
|
|
{
|
|
// Error or user canceled
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
UpdateLabel(label1, "4/6: Decompressing package...");
|
|
Thread.Sleep(500);
|
|
if(!Unpack(updateFolder + downloadFile, Application.StartupPath))
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
UpdateLabel(label1, "5/6: Moving files...");
|
|
Thread.Sleep(500);
|
|
MoveFiles();
|
|
|
|
UpdateLabel(label1, "6/6: Wrapping up...");
|
|
Thread.Sleep(500);
|
|
PostDownload();
|
|
}
|
|
|
|
private bool EditorClosed()
|
|
{
|
|
try
|
|
{
|
|
Process[] processes = Process.GetProcesses();
|
|
List<Process> toclose = new List<Process>();
|
|
|
|
// Gather all running editor processes...
|
|
foreach(Process process in processes)
|
|
{
|
|
if(process.ProcessName == processToEnd && Path.GetDirectoryName(process.MainModule.FileName) == Application.StartupPath)
|
|
{
|
|
toclose.Add(process);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ask the user how to proceed...
|
|
if(toclose.Count > 0)
|
|
{
|
|
TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Paused);
|
|
|
|
switch(MessageBox.Show(this, "The editor needs to be closed.\n\n"
|
|
+ "Press \"Abort\" to cancel the update.\n"
|
|
+ "Close the editor, then press \"Retry\" to proceed with the update.\n"
|
|
+ "Press \"Ignore\" to terminate the editor and proceed with the update.",
|
|
MESSAGEBOX_TITLE, MessageBoxButtons.AbortRetryIgnore))
|
|
{
|
|
case DialogResult.Abort: return false;
|
|
case DialogResult.Retry: return EditorClosed();
|
|
case DialogResult.Ignore:
|
|
UpdateLabel(label1, "3/6: Stopping " + processToEnd);
|
|
Thread.Sleep(500);
|
|
foreach(Process p in toclose) if(p != null) p.Kill();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
ErrorDescription = "Failed to stop the main process...\n" + ex.Message;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void StopBackgroundWorker()
|
|
{
|
|
if(worker != null && !worker.CancellationPending)
|
|
{
|
|
me.UpdateLabel(me.label1, "Stopping Background Thread...");
|
|
worker.CancelAsync();
|
|
while(worker.IsBusy) Application.DoEvents();
|
|
}
|
|
}
|
|
|
|
private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
|
{
|
|
InvokeClose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ======================== Methods
|
|
|
|
private void UpdateLabel(Label label, string text)
|
|
{
|
|
if(label.InvokeRequired)
|
|
{
|
|
SetLabelCallback d = UpdateLabel;
|
|
label.Invoke(d, new object[] { label, text });
|
|
}
|
|
else
|
|
{
|
|
label.Text = text;
|
|
label.Refresh();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private void InvokeClose()
|
|
{
|
|
if(this.Disposing || this.IsDisposed) return;
|
|
if(this.InvokeRequired)
|
|
{
|
|
CloseDelegate d = Close;
|
|
this.Invoke(d);
|
|
}
|
|
else
|
|
{
|
|
if(!appclosing && !string.IsNullOrEmpty(ErrorDescription))
|
|
{
|
|
if(!string.IsNullOrEmpty(URL))
|
|
{
|
|
ErrorDescription += Environment.NewLine + Environment.NewLine + "Would you like to download the update manually?";
|
|
TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Error);
|
|
if(MessageBox.Show(this, ErrorDescription, MESSAGEBOX_TITLE, MessageBoxButtons.YesNo) == DialogResult.Yes)
|
|
Process.Start(URL);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show(this, ErrorDescription, MESSAGEBOX_TITLE, MessageBoxButtons.OK);
|
|
}
|
|
}
|
|
|
|
WrapUp();
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private bool UpdateRequired()
|
|
{
|
|
// Get local revision number
|
|
int localrev = -1;
|
|
if(File.Exists(appFileName))
|
|
{
|
|
var info = FileVersionInfo.GetVersionInfo(appFileName);
|
|
localrev = info.ProductPrivatePart;
|
|
}
|
|
|
|
// Get remote revision number
|
|
int remoterev;
|
|
using(MemoryStream stream = Webdata.DownloadWebFile(Path.Combine(URL, "Versions.txt")))
|
|
{
|
|
if(stream == null)
|
|
{
|
|
if(string.IsNullOrEmpty(ErrorDescription)) ErrorDescription = "Failed to retrieve remote revision info.";
|
|
return false;
|
|
}
|
|
|
|
string s;
|
|
using(StreamReader reader = new StreamReader(stream))
|
|
{
|
|
s = reader.ReadLine(); // First line should be editor revision
|
|
}
|
|
|
|
if(!int.TryParse(s, out remoterev))
|
|
{
|
|
ErrorDescription = "Failed to retrieve remote revision number.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Replace wildcard with remoterev
|
|
downloadFile = downloadFile.Replace(revisionwildcard, remoterev.ToString());
|
|
|
|
if(remoterev > 0 && remoterev <= localrev)
|
|
{
|
|
URL = string.Empty;
|
|
ErrorDescription = "Your version is up to date!";
|
|
}
|
|
return remoterev > localrev;
|
|
}
|
|
|
|
private bool LoadConfig(string filename)
|
|
{
|
|
string[] lines = File.ReadAllLines(filename);
|
|
foreach (string line in lines)
|
|
{
|
|
if(line.StartsWith("URL"))
|
|
{
|
|
URL = line.Substring(3).Trim();
|
|
}
|
|
else if(line.StartsWith("FileName"))
|
|
{
|
|
appFileName = line.Substring(8).Trim();
|
|
processToEnd = Path.GetFileNameWithoutExtension(appFileName);
|
|
}
|
|
else if(line.StartsWith("UpdateName"))
|
|
{
|
|
downloadFile = line.Substring(10).Trim();
|
|
}
|
|
}
|
|
|
|
// Sanity cheks
|
|
if(string.IsNullOrEmpty(URL))
|
|
{
|
|
ErrorDescription = "URL is not specified in " + filename + "!";
|
|
return false;
|
|
}
|
|
|
|
if(string.IsNullOrEmpty(appFileName) || string.IsNullOrEmpty(processToEnd))
|
|
{
|
|
ErrorDescription = "FileName is not specified in " + filename + "!";
|
|
return false;
|
|
}
|
|
|
|
if(string.IsNullOrEmpty(downloadFile) || !downloadFile.Contains(revisionwildcard))
|
|
{
|
|
ErrorDescription = "UpdateName is invalid or not specified in " + filename + "!";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool Unpack(string file, string unZipTo)
|
|
{
|
|
try
|
|
{
|
|
using(IArchive arc = ArchiveFactory.Open(file))
|
|
{
|
|
if(!arc.IsComplete)
|
|
{
|
|
ErrorDescription = "Update failed: downloaded file is not complete...";
|
|
return false;
|
|
}
|
|
|
|
IReader reader = arc.ExtractAllEntries();
|
|
|
|
// Get number of files...
|
|
int curentry = 0;
|
|
int totalentries = arc.NumEntries;
|
|
|
|
string ourname = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
|
|
|
|
// Unpack all
|
|
ExtractionOptions options = new ExtractionOptions {ExtractFullPath = true, Overwrite = true};
|
|
while(reader.MoveToNextEntry())
|
|
{
|
|
if(appclosing) break;
|
|
if(reader.Entry.IsDirectory || Path.GetFileName(reader.Entry.Key) == ourname) continue; // Don't try to overrite ourselves...
|
|
reader.WriteEntryToDirectory(unZipTo, options);
|
|
UpdateProgressBar(new ByteArgs { Downloaded = curentry++, Total = totalentries }, 1, 2);
|
|
}
|
|
}
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
ErrorDescription = "Update failed: failed to unpack the update...\n" + e.Message;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool CheckPremissions(string path)
|
|
{
|
|
try
|
|
{
|
|
DirectoryInfo di = new DirectoryInfo(path);
|
|
DirectorySecurity acl = di.GetAccessControl();
|
|
AuthorizationRuleCollection rules = acl.GetAccessRules(true, true, typeof(NTAccount));
|
|
|
|
WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
|
|
WindowsPrincipal principal = new WindowsPrincipal(currentUser);
|
|
foreach(AuthorizationRule rule in rules)
|
|
{
|
|
FileSystemAccessRule fsAccessRule = rule as FileSystemAccessRule;
|
|
if(fsAccessRule == null) continue;
|
|
|
|
if((fsAccessRule.FileSystemRights & FileSystemRights.WriteData) > 0)
|
|
{
|
|
NTAccount ntAccount = rule.IdentityReference as NTAccount;
|
|
if(ntAccount == null) continue;
|
|
if(principal.IsInRole(ntAccount.Value)) return true;
|
|
}
|
|
}
|
|
}
|
|
catch(UnauthorizedAccessException) { }
|
|
|
|
return false;
|
|
}
|
|
|
|
private void PreDownload()
|
|
{
|
|
if(!Directory.Exists(updateFolder)) Directory.CreateDirectory(updateFolder);
|
|
}
|
|
|
|
private void PostDownload()
|
|
{
|
|
if(!File.Exists(appFileName))
|
|
{
|
|
ErrorDescription = "Unable to located updated executable ('" + appFileName + "')";
|
|
return;
|
|
}
|
|
|
|
if(appclosing) return;
|
|
Process.Start(new ProcessStartInfo { FileName = appFileName });
|
|
}
|
|
|
|
private void WrapUp()
|
|
{
|
|
if(Directory.Exists(updateFolder)) Directory.Delete(updateFolder, true);
|
|
}
|
|
|
|
private void MoveFiles()
|
|
{
|
|
DirectoryInfo di = new DirectoryInfo(updateFolder);
|
|
FileInfo[] files = di.GetFiles();
|
|
|
|
foreach(FileInfo fi in files)
|
|
{
|
|
if(fi.Name != downloadFile) File.Copy(updateFolder + fi.Name, Application.StartupPath + fi.Name, true);
|
|
}
|
|
}
|
|
|
|
private void UpdateProgressBar(ByteArgs e, int step, int totalsteps)
|
|
{
|
|
if(progressbar.InvokeRequired)
|
|
{
|
|
UpdateProgressBarCallback d = UpdateProgressBar;
|
|
progressbar.Invoke(d, new object[] { e, step, totalsteps });
|
|
}
|
|
else
|
|
{
|
|
int stepsize = (int)Math.Round((float)progressbar.Maximum / totalsteps);
|
|
float ratio = (float)e.Downloaded / e.Total;
|
|
int val = (int)Math.Floor(stepsize * step + stepsize * ratio);
|
|
|
|
if(val <= progressbar.Maximum)
|
|
{
|
|
progressbar.Value = val;
|
|
TaskbarProgress.SetValue(this.Handle, progressbar.Value, progressbar.Maximum);
|
|
}
|
|
progressbar.Refresh();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ======================== Events
|
|
|
|
private void MainForm_Load(object sender, EventArgs e)
|
|
{
|
|
Version version = Assembly.GetEntryAssembly().GetName().Version;
|
|
this.Text += " v" + version.Major + "." + version.Revision.ToString("#00");
|
|
|
|
worker = new BackgroundWorker();
|
|
worker.DoWork += BackgroundWorker;
|
|
worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted;
|
|
worker.WorkerSupportsCancellation = true;
|
|
worker.RunWorkerAsync();
|
|
}
|
|
|
|
private void cancel_Click(object sender, EventArgs e)
|
|
{
|
|
ErrorDescription = string.Empty;
|
|
appclosing = true;
|
|
StopBackgroundWorker();
|
|
InvokeClose();
|
|
}
|
|
|
|
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
appclosing = true;
|
|
StopBackgroundWorker();
|
|
}
|
|
|
|
private void WebdataOnBytesDownloaded(ByteArgs ba)
|
|
{
|
|
UpdateProgressBar(ba, 0, 2);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |