From 2dc3ced5cf3b3901068e905de21f798bc667feb5 Mon Sep 17 00:00:00 2001 From: ptsurbeleu Date: Tue, 14 Feb 2012 23:15:45 -0800 Subject: [PATCH] Added bit.ly support for distiributives download; Updated year in copyright text; --- .../Sources/Setup/Setup.vdproj | 55 +- WebsitePanel.Installer/Sources/VersionInfo.cs | 10 +- .../Common/FileUtils.cs | 425 +++++----- .../Common/Utils.cs | 21 +- .../WebsitePanel.Installer.Core/Loader.cs | 791 ++++++++++-------- .../ServiceComponentNotFoundException.cs | 17 + .../WebsitePanel.Installer.Core.csproj | 2 + .../Controls/ComponentControl.cs | 16 +- .../Controls/ComponentsControl.cs | 504 +++++------ .../WebsitePanel.Installer/Controls/Loader.cs | 240 +++--- .../WebsitePanel.Installer/Updater.exe | Bin 199168 -> 199168 bytes .../Wizard/ExpressInstallPage.cs | 84 +- 12 files changed, 1210 insertions(+), 955 deletions(-) create mode 100644 WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/ServiceComponentNotFoundException.cs diff --git a/WebsitePanel.Installer/Sources/Setup/Setup.vdproj b/WebsitePanel.Installer/Sources/Setup/Setup.vdproj index b8775a0e..836c2919 100644 --- a/WebsitePanel.Installer/Sources/Setup/Setup.vdproj +++ b/WebsitePanel.Installer/Sources/Setup/Setup.vdproj @@ -63,6 +63,24 @@ } "Entry" { + "MsmKey" = "8:_5FD334A6C47943FA9A98232EA921C90B" + "OwnerKey" = "8:_05F59A142DD147798C90054A203C0EE9" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_5FD334A6C47943FA9A98232EA921C90B" + "OwnerKey" = "8:_1239E87E938248B1BAF9BF75C32D3EDC" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_5FD334A6C47943FA9A98232EA921C90B" + "OwnerKey" = "8:_CFB0AE8275767700870555C8CDA92C96" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_BD9DC4338DFD4472BE5D099C388608B6" "OwnerKey" = "8:_UNDEFINED" "MsmSig" = "8:_UNDEFINED" @@ -294,6 +312,37 @@ "IsDependency" = "11:FALSE" "IsolateTo" = "8:" } + "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_5FD334A6C47943FA9A98232EA921C90B" + { + "AssemblyRegister" = "3:1" + "AssemblyIsInGAC" = "11:FALSE" + "AssemblyAsmDisplayName" = "8:Ionic.Zip.Reduced, Version=1.8.4.28, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c, processorArchitecture=MSIL" + "ScatterAssemblies" + { + "_5FD334A6C47943FA9A98232EA921C90B" + { + "Name" = "8:Ionic.Zip.Reduced.dll" + "Attributes" = "3:512" + } + } + "SourcePath" = "8:Ionic.Zip.Reduced.dll" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_E742E59BFE4D43C59AA65A07792B89FB" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:TRUE" + "IsolateTo" = "8:" + } "{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_BD9DC4338DFD4472BE5D099C388608B6" { "SourcePath" = "8:Banner.bmp" @@ -407,15 +456,15 @@ { "Name" = "8:Microsoft Visual Studio" "ProductName" = "8:WebsitePanel Installer" - "ProductCode" = "8:{09BCDF68-1964-4FC6-9570-9AB3B8512CF6}" - "PackageCode" = "8:{D94FF2E6-1D7D-4E33-8528-C7B6BFADEC99}" + "ProductCode" = "8:{A22F374C-4AFC-4B5D-A509-7456A6107588}" + "PackageCode" = "8:{401F157D-6D55-4F66-A2C6-419F87BBF97D}" "UpgradeCode" = "8:{2950A907-11E7-436C-86CE-049C414AFD08}" "AspNetVersion" = "8:4.0.30319.0" "RestartWWWService" = "11:FALSE" "RemovePreviousVersions" = "11:FALSE" "DetectNewerInstalledVersion" = "11:FALSE" "InstallAllUsers" = "11:TRUE" - "ProductVersion" = "8:1.2.0" + "ProductVersion" = "8:1.2.1" "Manufacturer" = "8:Outercurve Foundation" "ARPHELPTELEPHONE" = "8:" "ARPHELPLINK" = "8:" diff --git a/WebsitePanel.Installer/Sources/VersionInfo.cs b/WebsitePanel.Installer/Sources/VersionInfo.cs index d03067e7..f939546f 100644 --- a/WebsitePanel.Installer/Sources/VersionInfo.cs +++ b/WebsitePanel.Installer/Sources/VersionInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -35,7 +35,7 @@ using System.Reflection; // Revision // [assembly: AssemblyCompany("Outercurve Foundation")] -[assembly: AssemblyCopyright("Copyright © 2011 Outercurve Foundation.")] -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.38")] -[assembly: AssemblyInformationalVersion("1.2.0")] \ No newline at end of file +[assembly: AssemblyCopyright("Copyright © 2012 Outercurve Foundation.")] +[assembly: AssemblyVersion("1.2.1.0")] +[assembly: AssemblyFileVersion("1.2.1.0")] +[assembly: AssemblyInformationalVersion("1.2.1")] \ No newline at end of file diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/FileUtils.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/FileUtils.cs index 7ca351b9..fb93c644 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/FileUtils.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/FileUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -34,43 +34,43 @@ using System.IO; namespace WebsitePanel.Installer.Common { - /// - /// File utils. - /// - public sealed class FileUtils - { - /// - /// Initializes a new instance of the class. - /// - private FileUtils() - { - } + /// + /// File utils. + /// + public sealed class FileUtils + { + /// + /// Initializes a new instance of the class. + /// + private FileUtils() + { + } - /// - /// Creates drectory with the specified directory. - /// - /// The directory path to create. - public static void CreateDirectory(string path) - { - string dir = Path.GetDirectoryName(path); - if(!Directory.Exists(dir)) - { - // create directory structure - Directory.CreateDirectory(dir); - } - } + /// + /// Creates drectory with the specified directory. + /// + /// The directory path to create. + public static void CreateDirectory(string path) + { + string dir = Path.GetDirectoryName(path); + if(!Directory.Exists(dir)) + { + // create directory structure + Directory.CreateDirectory(dir); + } + } - /// - /// Saves file content. - /// - /// File name. - /// The array of bytes to write. - public static void SaveFileContent(string fileName, byte[] content) - { - FileStream stream = new FileStream(fileName, FileMode.Create); - stream.Write(content, 0, content.Length); - stream.Close(); - } + /// + /// Saves file content. + /// + /// File name. + /// The array of bytes to write. + public static void SaveFileContent(string fileName, byte[] content) + { + FileStream stream = new FileStream(fileName, FileMode.Create); + stream.Write(content, 0, content.Length); + stream.Close(); + } /// /// Saves file content. @@ -84,188 +84,203 @@ namespace WebsitePanel.Installer.Common stream.Close(); } - /// - /// Deletes the specified file. - /// - /// The name of the file to be deleted. - public static void DeleteFile(string fileName) - { - int attempts = 0; - while (true) - { - try - { - DeleteFileInternal(fileName); - break; - } - catch (Exception) - { - if (attempts > 2) - throw; + /// + /// Deletes the specified file. + /// + /// The name of the file to be deleted. + public static void DeleteFile(string fileName) + { + int attempts = 0; + while (true) + { + try + { + DeleteFileInternal(fileName); + break; + } + catch (Exception) + { + if (attempts > 2) + throw; - attempts++; - System.Threading.Thread.Sleep(1000); - } - } - } + attempts++; + System.Threading.Thread.Sleep(1000); + } + } + } - /// - /// Deletes the specified file. - /// - /// The name of the file to be deleted. - private static void DeleteReadOnlyFile(string fileName) - { - FileInfo info = new FileInfo(fileName); - info.Attributes = FileAttributes.Normal; - info.Delete(); - } + /// + /// Deletes the specified file. + /// + /// The name of the file to be deleted. + private static void DeleteReadOnlyFile(string fileName) + { + FileInfo info = new FileInfo(fileName); + info.Attributes = FileAttributes.Normal; + info.Delete(); + } - /// - /// Deletes the specified file. - /// - /// The name of the file to be deleted. - private static void DeleteFileInternal(string fileName) - { - try - { - File.Delete(fileName); - } - catch (UnauthorizedAccessException) - { - DeleteReadOnlyFile(fileName); - } - } + /// + /// Deletes the specified file. + /// + /// The name of the file to be deleted. + private static void DeleteFileInternal(string fileName) + { + try + { + File.Delete(fileName); + } + catch (UnauthorizedAccessException) + { + DeleteReadOnlyFile(fileName); + } + } - /// - /// Deletes the specified directory. - /// - /// The name of the directory to be deleted. - public static void DeleteDirectory(string directory) - { - if (!Directory.Exists(directory)) - return; + /// + /// Deletes the specified directory. + /// + /// The name of the directory to be deleted. + public static void DeleteDirectory(string directory) + { + if (!Directory.Exists(directory)) + return; - // iterate through child folders - string[] dirs = Directory.GetDirectories(directory); - foreach (string dir in dirs) - { - DeleteDirectory(dir); - } + // iterate through child folders + string[] dirs = Directory.GetDirectories(directory); + foreach (string dir in dirs) + { + DeleteDirectory(dir); + } - // iterate through child files - string[] files = Directory.GetFiles(directory); - foreach (string file in files) - { - DeleteFile(file); - } + // iterate through child files + string[] files = Directory.GetFiles(directory); + foreach (string file in files) + { + DeleteFile(file); + } - //try to delete dir for 3 times - int attempts = 0; - while (true) - { - try - { - DeleteDirectoryInternal(directory); - break; - } - catch (Exception) - { - if (attempts > 2) - throw; + //try to delete dir for 3 times + int attempts = 0; + while (true) + { + try + { + DeleteDirectoryInternal(directory); + break; + } + catch (Exception) + { + if (attempts > 2) + throw; - attempts++; - System.Threading.Thread.Sleep(1000); - } - } - } + attempts++; + System.Threading.Thread.Sleep(1000); + } + } + } - /// - /// Deletes the specified directory. - /// - /// The name of the directory to be deleted. - private static void DeleteDirectoryInternal(string directory) - { - try - { - Directory.Delete(directory); - } - catch (IOException) - { - DeleteReadOnlyDirectory(directory); - } - } + /// + /// Deletes the specified directory. + /// + /// The name of the directory to be deleted. + private static void DeleteDirectoryInternal(string directory) + { + try + { + Directory.Delete(directory); + } + catch (IOException) + { + DeleteReadOnlyDirectory(directory); + } + } - /// - /// Deletes the specified directory. - /// - /// The name of the directory to be deleted. - private static void DeleteReadOnlyDirectory(string directory) - { - DirectoryInfo info = new DirectoryInfo(directory); - info.Attributes = FileAttributes.Normal; - info.Delete(); - } + /// + /// Deletes the specified directory. + /// + /// The name of the directory to be deleted. + private static void DeleteReadOnlyDirectory(string directory) + { + DirectoryInfo info = new DirectoryInfo(directory); + info.Attributes = FileAttributes.Normal; + info.Delete(); + } - /// - /// Determines whether the specified file exists. - /// - /// The path to check. - /// - public static bool FileExists(string fileName) - { - return File.Exists(fileName); - } + /// + /// Determines whether the specified file exists. + /// + /// The path to check. + /// + public static bool FileExists(string fileName) + { + return File.Exists(fileName); + } - /// - /// Determines whether the given path refers to an existing directory on disk. - /// - /// The path to test. - /// - public static bool DirectoryExists(string path) - { - return Directory.Exists(path); - } - - /// - /// Returns current application path. - /// - /// Curent application path. - public static string GetCurrentDirectory() - { - return AppDomain.CurrentDomain.BaseDirectory; - } + /// + /// Determines whether the given path refers to an existing directory on disk. + /// + /// The path to test. + /// + public static bool DirectoryExists(string path) + { + return Directory.Exists(path); + } + + /// + /// Returns current application path. + /// + /// Curent application path. + public static string GetCurrentDirectory() + { + return AppDomain.CurrentDomain.BaseDirectory; + } - /// - /// Returns application temp directory. - /// - /// Application temp directory. - public static string GetTempDirectory() - { - return Path.Combine(GetCurrentDirectory(), "Tmp"); - } + /// + /// Returns application temp directory. + /// + /// Application temp directory. + public static string GetTempDirectory() + { + return Path.Combine(GetCurrentDirectory(), "Tmp"); + } - /// - /// Returns application data directory. - /// - /// Application data directory. - public static string GetDataDirectory() - { - return Path.Combine(GetCurrentDirectory(), "Data"); - } + /// + /// Returns application data directory. + /// + /// Application data directory. + public static string GetDataDirectory() + { + return Path.Combine(GetCurrentDirectory(), "Data"); + } - /// - /// Deletes application temp directory. - /// - public static void DeleteTempDirectory() - { - try - { - DeleteDirectory(GetTempDirectory()); - } - catch (Exception ex) - { - Log.WriteError("IO Error", ex); - } - } - } + /// + /// Deletes application's Data folder. + /// + public static void DeleteDataDirectory() + { + try + { + DeleteDirectory(GetDataDirectory()); + } + catch (Exception ex) + { + Log.WriteError("IO Error", ex); + } + } + + /// + /// Deletes application Tmp directory. + /// + public static void DeleteTempDirectory() + { + try + { + DeleteDirectory(GetTempDirectory()); + } + catch (Exception ex) + { + Log.WriteError("IO Error", ex); + } + } + } } diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/Utils.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/Utils.cs index 68b4cc31..cd036dd1 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/Utils.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Common/Utils.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -45,6 +45,7 @@ using System.Threading; using WebsitePanel.Installer.Core; using WebsitePanel.Installer.Configuration; using System.Xml; +using System.Data; namespace WebsitePanel.Installer.Common { @@ -389,5 +390,23 @@ namespace WebsitePanel.Installer.Common mutex = new Mutex(true, "WebsitePanel Installer", out createdNew); return createdNew; } + + public static string GetDistributiveLocationInfo(string ccode, string cversion) + { + var service = ServiceProviderProxy.GetInstallerWebService(); + // + DataSet ds = service.GetReleaseFileInfo(ccode, cversion); + // + if (ds == null || ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0) + { + Log.WriteInfo("Component code: {0}; Component version: {1};", ccode, cversion); + // + throw new ServiceComponentNotFoundException("Seems that the Service has no idea about the component requested."); + } + // + DataRow row = ds.Tables[0].Rows[0]; + // + return row["FullFilePath"].ToString(); + } } } diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Loader.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Loader.cs index cc38ca29..39fe1861 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Loader.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/Loader.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -36,380 +36,495 @@ using System.Text; using Ionic.Zip; using WebsitePanel.Installer.Common; +using System.Net; +using System.Text.RegularExpressions; +using System.Collections; +using System.Threading.Tasks; +using System.Reflection; +using System.Diagnostics; namespace WebsitePanel.Installer.Core { - public class LoaderEventArgs : EventArgs - { - public string StatusMessage { get; set; } - public T EventData { get; set; } - public bool Cancellable { get; set; } - } + public class LoaderEventArgs : EventArgs + { + public string StatusMessage { get; set; } + public T EventData { get; set; } + public bool Cancellable { get; set; } + } - /// - /// Loader form. - /// - public partial class Loader - { - public const string ConnectingRemotServiceMessage = "Connecting..."; - public const string DownloadingSetupFilesMessage = "Downloading setup files..."; - public const string CopyingSetupFilesMessage = "Copying setup files..."; - public const string PreparingSetupFilesMessage = "Please wait while Setup prepares the necessary files..."; - public const string DownloadProgressMessage = "{0} KB of {1} KB"; - public const string PrepareSetupProgressMessage = "{0}%"; + public static class LoaderFactory + { + /// + /// Instantiates either BitlyLoader or InstallerServiceLoader based on remote file format. + /// + /// + /// + public static Loader CreateFileLoader(string remoteFile) + { + Debug.Assert(!String.IsNullOrEmpty(remoteFile), "Remote file is empty"); - private const int ChunkSize = 262144; - private Thread thread; - private string localFile; - private string remoteFile; - private string componentCode; - private string version; + if (remoteFile.StartsWith("http://bit.ly/")) + { + return new BitlyLoader(remoteFile); + } + else + { + return new Loader(remoteFile); + } + } + } - public event EventHandler> StatusChanged; - public event EventHandler> OperationFailed; - public event EventHandler> ProgressChanged; - public event EventHandler OperationCompleted; + public class BitlyLoader : Loader + { + public const string WEB_PI_USER_AGENT_HEADER = "PI-Integrator/3.0.0.0({0})"; - public Loader(string remoteFile) - { - this.remoteFile = remoteFile; - } + private WebClient fileLoader; - public Loader(string localFile, string componentCode, string version) - { - this.localFile = localFile; - this.componentCode = componentCode; - this.version = version; - } + public BitlyLoader(string remoteFile) + : base(remoteFile) + { + InitFileLoader(); + } - public void LoadAppDistributive() - { - thread = new Thread(new ThreadStart(LoadAppDistributiveInternal)); - thread.Start(); - } + private void InitFileLoader() + { + fileLoader = new WebClient(); + // Set HTTP header for Codeplex to allow direct downloads + fileLoader.Headers.Add("User-Agent", String.Format(WEB_PI_USER_AGENT_HEADER, Assembly.GetExecutingAssembly().FullName)); + } - private void RaiseOnStatusChangedEvent(string statusMessage) - { - RaiseOnStatusChangedEvent(statusMessage, String.Empty); - } + protected override Task GetDownloadFileTask(string remoteFile, string tmpFile, CancellationToken ct) + { + var downloadFileTask = new Task(() => + { + if (!File.Exists(tmpFile)) + { + // Mimic synchronous file download operation because we need to track the download progress + // and be able to cancel the operation in progress + AutoResetEvent autoEvent = new AutoResetEvent(false); - private void RaiseOnStatusChangedEvent(string statusMessage, string eventData) - { - RaiseOnStatusChangedEvent(statusMessage, eventData, true); - } + if (fileLoader.IsBusy.Equals(true)) + { + return; + } - private void RaiseOnStatusChangedEvent(string statusMessage, string eventData, bool cancellable) - { - if (StatusChanged == null) - { - return; - } - // No event data for status updates - StatusChanged(this, new LoaderEventArgs { - StatusMessage = statusMessage, - EventData = eventData, - Cancellable = cancellable - }); - } + ct.Register(() => + { + fileLoader.CancelAsync(); + }); - private void RaiseOnProgressChangedEvent(int eventData) - { - RaiseOnProgressChangedEvent(eventData, true); - } + Log.WriteStart("Downloading file"); + Log.WriteInfo("Downloading file \"{0}\" to \"{1}\"", remoteFile, tmpFile); + + // Attach event handlers to track status of the download process + fileLoader.DownloadProgressChanged += (obj, e) => + { + if (ct.IsCancellationRequested) + return; - private void RaiseOnProgressChangedEvent(int eventData, bool cancellable) - { - if (ProgressChanged == null) - { - return; - } - // - ProgressChanged(this, new LoaderEventArgs { - EventData = eventData, - Cancellable = cancellable - }); - } + RaiseOnProgressChangedEvent(e.ProgressPercentage); + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, + String.Format(DownloadProgressMessage, e.BytesReceived / 1024, e.TotalBytesToReceive / 1024)); + }; - private void RaiseOnOperationFailedEvent(Exception ex) - { - if (OperationFailed == null) - { - return; - } - // - OperationFailed(this, new LoaderEventArgs { EventData = ex }); - } + fileLoader.DownloadFileCompleted += (obj, e) => + { + if (ct.IsCancellationRequested == false) + { + RaiseOnProgressChangedEvent(100); + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, "100%"); + } - private void RaiseOnOperationCompletedEvent() - { - if (OperationCompleted == null) - { - return; - } - // - OperationCompleted(this, EventArgs.Empty); - } + if (e.Cancelled) + { + CancelDownload(tmpFile); + } - /// - /// Displays process progress. - /// - private void LoadAppDistributiveInternal() - { - // - try - { - var service = ServiceProviderProxy.GetInstallerWebService(); - // - string dataFolder = FileUtils.GetDataDirectory(); - string tmpFolder = FileUtils.GetTempDirectory(); + autoEvent.Set(); + }; - if (!Directory.Exists(dataFolder)) - { - Directory.CreateDirectory(dataFolder); - Log.WriteInfo("Data directory created"); - } + fileLoader.DownloadFileAsync(new Uri(remoteFile), tmpFile); + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage); + + autoEvent.WaitOne(); + } + }, ct); - if (Directory.Exists(tmpFolder)) - { - FileUtils.DeleteTempDirectory(); - } + return downloadFileTask; + } + } - if (!Directory.Exists(tmpFolder)) - { - Directory.CreateDirectory(tmpFolder); - Log.WriteInfo("Tmp directory created"); - } + /// + /// Loader form. + /// + public class Loader + { + public const string ConnectingRemotServiceMessage = "Connecting..."; + public const string DownloadingSetupFilesMessage = "Downloading setup files..."; + public const string CopyingSetupFilesMessage = "Copying setup files..."; + public const string PreparingSetupFilesMessage = "Please wait while Setup prepares the necessary files..."; + public const string DownloadProgressMessage = "{0} KB of {1} KB"; + public const string PrepareSetupProgressMessage = "{0}%"; - string fileToDownload = null; - if (!string.IsNullOrEmpty(localFile)) - { - fileToDownload = localFile; - } - else - { - fileToDownload = Path.GetFileName(remoteFile); - } + private const int ChunkSize = 262144; + private string remoteFile; + private CancellationTokenSource cts; - string destinationFile = Path.Combine(dataFolder, fileToDownload); - string tmpFile = Path.Combine(tmpFolder, fileToDownload); + public event EventHandler> StatusChanged; + public event EventHandler> OperationFailed; + public event EventHandler> ProgressChanged; + public event EventHandler OperationCompleted; - //check whether file already downloaded - if (!File.Exists(destinationFile)) - { - if (string.IsNullOrEmpty(remoteFile)) - { - //need to get remote file name - RaiseOnStatusChangedEvent(ConnectingRemotServiceMessage); - // - RaiseOnProgressChangedEvent(0); - // - DataSet ds = service.GetReleaseFileInfo(componentCode, version); - // - RaiseOnProgressChangedEvent(100); - // - if (ds != null && - ds.Tables.Count > 0 && - ds.Tables[0].Rows.Count > 0) - { - DataRow row = ds.Tables[0].Rows[0]; - remoteFile = row["FullFilePath"].ToString(); - fileToDownload = Path.GetFileName(remoteFile); - destinationFile = Path.Combine(dataFolder, fileToDownload); - tmpFile = Path.Combine(tmpFolder, fileToDownload); - } - else - { - throw new Exception("Installer not found"); - } - } + public Loader(string remoteFile) + { + this.remoteFile = remoteFile; + } - // download file to tmp folder - RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage); - // - RaiseOnProgressChangedEvent(0); - // - DownloadFile(remoteFile, tmpFile); - // - RaiseOnProgressChangedEvent(100); + public void LoadAppDistributive() + { + ThreadPool.QueueUserWorkItem(q => LoadAppDistributiveInternal()); + } - // copy downloaded file to data folder - RaiseOnStatusChangedEvent(CopyingSetupFilesMessage); - // - RaiseOnProgressChangedEvent(0); + protected void RaiseOnStatusChangedEvent(string statusMessage) + { + RaiseOnStatusChangedEvent(statusMessage, String.Empty); + } - // Ensure that the target does not exist. - if (File.Exists(destinationFile)) - FileUtils.DeleteFile(destinationFile); - File.Move(tmpFile, destinationFile); - // - RaiseOnProgressChangedEvent(100); - } + protected void RaiseOnStatusChangedEvent(string statusMessage, string eventData) + { + RaiseOnStatusChangedEvent(statusMessage, eventData, true); + } - // unzip file - RaiseOnStatusChangedEvent(PreparingSetupFilesMessage); - // - RaiseOnProgressChangedEvent(0); - // - UnzipFile(destinationFile, tmpFolder); - // - RaiseOnProgressChangedEvent(100); - // - RaiseOnOperationCompletedEvent(); - } - catch (Exception ex) - { - if (Utils.IsThreadAbortException(ex)) - return; + protected void RaiseOnStatusChangedEvent(string statusMessage, string eventData, bool cancellable) + { + if (StatusChanged == null) + { + return; + } + // No event data for status updates + StatusChanged(this, new LoaderEventArgs + { + StatusMessage = statusMessage, + EventData = eventData, + Cancellable = cancellable + }); + } - Log.WriteError("Loader module error", ex); - // - RaiseOnOperationFailedEvent(ex); - } - } + protected void RaiseOnProgressChangedEvent(int eventData) + { + RaiseOnProgressChangedEvent(eventData, true); + } - private void DownloadFile(string sourceFile, string destinationFile) - { - try - { - var service = ServiceProviderProxy.GetInstallerWebService(); - // - Log.WriteStart("Downloading file"); - Log.WriteInfo(string.Format("Downloading file \"{0}\" to \"{1}\"", sourceFile, destinationFile)); + protected void RaiseOnProgressChangedEvent(int eventData, bool cancellable) + { + if (ProgressChanged == null) + { + return; + } + // + ProgressChanged(this, new LoaderEventArgs + { + EventData = eventData, + Cancellable = cancellable + }); + } - long downloaded = 0; - long fileSize = service.GetFileSize(sourceFile); - if (fileSize == 0) - { - throw new FileNotFoundException("Service returned empty file.", sourceFile); - } + protected void RaiseOnOperationFailedEvent(Exception ex) + { + if (OperationFailed == null) + { + return; + } + // + OperationFailed(this, new LoaderEventArgs { EventData = ex }); + } - byte[] content; + protected void RaiseOnOperationCompletedEvent() + { + if (OperationCompleted == null) + { + return; + } + // + OperationCompleted(this, EventArgs.Empty); + } - while (downloaded < fileSize) - { - content = service.GetFileChunk(sourceFile, (int)downloaded, ChunkSize); - if (content == null) - { - throw new FileNotFoundException("Service returned NULL file content.", sourceFile); - } - FileUtils.AppendFileContent(destinationFile, content); - downloaded += content.Length; - //update progress bar - RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, - string.Format(DownloadProgressMessage, downloaded / 1024, fileSize / 1024)); - // - RaiseOnProgressChangedEvent(Convert.ToInt32((downloaded * 100) / fileSize)); + /// + /// Executes a file download request asynchronously + /// + private void LoadAppDistributiveInternal() + { + try + { + string dataFolder; + string tmpFolder; + // Retrieve local storage configuration + GetLocalStorageInfo(out dataFolder, out tmpFolder); + // Initialize storage + InitializeLocalStorage(dataFolder, tmpFolder); - if (content.Length < ChunkSize) - break; - } - // - RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, "100%"); - // - Log.WriteEnd(string.Format("Downloaded {0} bytes", downloaded)); - } - catch (Exception ex) - { - if (Utils.IsThreadAbortException(ex)) - return; + string fileToDownload = Path.GetFileName(remoteFile); - throw; - } - } + string destinationFile = Path.Combine(dataFolder, fileToDownload); + string tmpFile = Path.Combine(tmpFolder, fileToDownload); - private void UnzipFile(string zipFile, string destFolder) - { - try - { - int val = 0; - // Negative value means no progress made yet - int progress = -1; - // - Log.WriteStart("Unzipping file"); - Log.WriteInfo(string.Format("Unzipping file \"{0}\" to the folder \"{1}\"", zipFile, destFolder)); + cts = new CancellationTokenSource(); + CancellationToken token = cts.Token; - long zipSize = 0; - var zipInfo = ZipFile.Read(zipFile); - try - { - foreach (ZipEntry entry in zipInfo) - { - if (!entry.IsDirectory) - zipSize += entry.UncompressedSize; - } - } - finally - { - if (zipInfo != null) - { - zipInfo.Dispose(); - } - } + try + { + // Download the file requested + Task downloadFileTask = GetDownloadFileTask(remoteFile, tmpFile, token); + // Move the file downloaded from temporary location to Data folder + var moveFileTask = downloadFileTask.ContinueWith((t) => + { + if (File.Exists(tmpFile)) + { + // copy downloaded file to data folder + RaiseOnStatusChangedEvent(CopyingSetupFilesMessage); + // + RaiseOnProgressChangedEvent(0); - long unzipped = 0; - // - var zip = ZipFile.Read(zipFile); - // - try - { - foreach (ZipEntry entry in zip) - { - // - entry.Extract(destFolder, ExtractExistingFileAction.OverwriteSilently); - // - if (!entry.IsDirectory) - unzipped += entry.UncompressedSize; + // Ensure that the target does not exist. + if (File.Exists(destinationFile)) + FileUtils.DeleteFile(destinationFile); + File.Move(tmpFile, destinationFile); + // + RaiseOnProgressChangedEvent(100); + } + }, TaskContinuationOptions.NotOnCanceled); + // Unzip file downloaded + var unzipFileTask = moveFileTask.ContinueWith((t) => + { + if (File.Exists(destinationFile)) + { + RaiseOnStatusChangedEvent(PreparingSetupFilesMessage); + // + RaiseOnProgressChangedEvent(0); + // + UnzipFile(destinationFile, tmpFolder); + // + RaiseOnProgressChangedEvent(100); + } + }, token); + // + var notifyCompletionTask = unzipFileTask.ContinueWith((t) => + { + RaiseOnOperationCompletedEvent(); + }, token); + + downloadFileTask.Start(); + downloadFileTask.Wait(); + } + catch (AggregateException ae) + { + ae.Handle((e) => + { + // We handle cancellation requests + if (e is OperationCanceledException) + { + CancelDownload(tmpFile); + Log.WriteInfo("Download has been cancelled by the user"); + return true; + } + // But other issues just being logged + Log.WriteError("Could not download the file", e); + return false; + }); + } + } + catch (Exception ex) + { + if (Utils.IsThreadAbortException(ex)) + return; - if (zipSize != 0) - { - val = Convert.ToInt32(unzipped * 100 / zipSize); - // Skip to raise the progress event change when calculated progress - // and the current progress value are even - if (val == progress) - { - continue; - } - // - RaiseOnStatusChangedEvent( - PreparingSetupFilesMessage, - String.Format(PrepareSetupProgressMessage, val), - false); - // - RaiseOnProgressChangedEvent(val, false); - } - } - // Notify client the operation can be cancelled at this time - RaiseOnProgressChangedEvent(100); - // - Log.WriteEnd("Unzipped file"); - } - finally - { - if (zip != null) - { - zip.Dispose(); - } - } - } - catch (Exception ex) - { - if (Utils.IsThreadAbortException(ex)) - return; - // - RaiseOnOperationFailedEvent(ex); - } - } + Log.WriteError("Loader module error", ex); + // + RaiseOnOperationFailedEvent(ex); + } + } - public void AbortOperation() - { - if (thread != null) - { - if (thread.IsAlive) - { - thread.Abort(); - } - thread.Join(); - } - } - } + protected virtual Task GetDownloadFileTask(string sourceFile, string tmpFile, CancellationToken ct) + { + var downloadFileTask = new Task(() => + { + if (!File.Exists(tmpFile)) + { + var service = ServiceProviderProxy.GetInstallerWebService(); + + RaiseOnProgressChangedEvent(0); + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage); + + Log.WriteStart("Downloading file"); + Log.WriteInfo(string.Format("Downloading file \"{0}\" to \"{1}\"", sourceFile, tmpFile)); + + long downloaded = 0; + long fileSize = service.GetFileSize(sourceFile); + if (fileSize == 0) + { + throw new FileNotFoundException("Service returned empty file.", sourceFile); + } + + byte[] content; + + while (downloaded < fileSize) + { + // Throw OperationCancelledException if there is an incoming cancel request + ct.ThrowIfCancellationRequested(); + + content = service.GetFileChunk(sourceFile, (int)downloaded, ChunkSize); + if (content == null) + { + throw new FileNotFoundException("Service returned NULL file content.", sourceFile); + } + FileUtils.AppendFileContent(tmpFile, content); + downloaded += content.Length; + // Update download progress + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, + string.Format(DownloadProgressMessage, downloaded / 1024, fileSize / 1024)); + + RaiseOnProgressChangedEvent(Convert.ToInt32((downloaded * 100) / fileSize)); + + if (content.Length < ChunkSize) + break; + } + + RaiseOnStatusChangedEvent(DownloadingSetupFilesMessage, "100%"); + Log.WriteEnd(string.Format("Downloaded {0} bytes", downloaded)); + } + }, ct); + + return downloadFileTask; + } + + private static void InitializeLocalStorage(string dataFolder, string tmpFolder) + { + if (!Directory.Exists(dataFolder)) + { + Directory.CreateDirectory(dataFolder); + Log.WriteInfo("Data directory created"); + } + + if (Directory.Exists(tmpFolder)) + { + Directory.Delete(tmpFolder, true); + } + + if (!Directory.Exists(tmpFolder)) + { + Directory.CreateDirectory(tmpFolder); + Log.WriteInfo("Tmp directory created"); + } + } + + private static void GetLocalStorageInfo(out string dataFolder, out string tmpFolder) + { + dataFolder = FileUtils.GetDataDirectory(); + tmpFolder = FileUtils.GetTempDirectory(); + } + + private void UnzipFile(string zipFile, string destFolder) + { + try + { + int val = 0; + // Negative value means no progress made yet + int progress = -1; + // + Log.WriteStart("Unzipping file"); + Log.WriteInfo(string.Format("Unzipping file \"{0}\" to the folder \"{1}\"", zipFile, destFolder)); + + long zipSize = 0; + var zipInfo = ZipFile.Read(zipFile); + try + { + foreach (ZipEntry entry in zipInfo) + { + if (!entry.IsDirectory) + zipSize += entry.UncompressedSize; + } + } + finally + { + if (zipInfo != null) + { + zipInfo.Dispose(); + } + } + + long unzipped = 0; + // + var zip = ZipFile.Read(zipFile); + // + try + { + foreach (ZipEntry entry in zip) + { + // + entry.Extract(destFolder, ExtractExistingFileAction.OverwriteSilently); + // + if (!entry.IsDirectory) + unzipped += entry.UncompressedSize; + + if (zipSize != 0) + { + val = Convert.ToInt32(unzipped * 100 / zipSize); + // Skip to raise the progress event change when calculated progress + // and the current progress value are even + if (val == progress) + { + continue; + } + // + RaiseOnStatusChangedEvent( + PreparingSetupFilesMessage, + String.Format(PrepareSetupProgressMessage, val), + false); + // + RaiseOnProgressChangedEvent(val, false); + } + } + // Notify client the operation can be cancelled at this time + RaiseOnProgressChangedEvent(100); + // + Log.WriteEnd("Unzipped file"); + } + finally + { + if (zip != null) + { + zip.Dispose(); + } + } + } + catch (Exception ex) + { + if (Utils.IsThreadAbortException(ex)) + return; + // + RaiseOnOperationFailedEvent(ex); + } + } + + /// + /// Cleans up temporary file if the download process has been cancelled. + /// + /// Path to the temporary file to cleanup + protected virtual void CancelDownload(string tmpFile) + { + if (File.Exists(tmpFile)) + { + File.Delete(tmpFile); + } + } + + public void AbortOperation() + { + // Make sure we are in business + if (cts != null) + { + cts.Cancel(); + } + } + } } \ No newline at end of file diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/ServiceComponentNotFoundException.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/ServiceComponentNotFoundException.cs new file mode 100644 index 00000000..d0f2ab78 --- /dev/null +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/ServiceComponentNotFoundException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebsitePanel.Installer.Core +{ + class ServiceComponentNotFoundException : Exception + { + private string p; + + public ServiceComponentNotFoundException(string p) + : base(p) + { + } + } +} diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/WebsitePanel.Installer.Core.csproj b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/WebsitePanel.Installer.Core.csproj index 954d8f1c..c33c09e2 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/WebsitePanel.Installer.Core.csproj +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer.Core/WebsitePanel.Installer.Core.csproj @@ -35,6 +35,7 @@ ..\..\Lib\Ionic.Zip.Reduced.dll + @@ -80,6 +81,7 @@ True Settings.settings + Code diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentControl.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentControl.cs index 2420e91a..d24c5383 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentControl.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentControl.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -211,7 +211,7 @@ namespace WebsitePanel.Installer.Controls { Log.WriteInfo(string.Format("Updating {0}", componentName)); //download installer - Loader form = new Loader(this.AppContext, fileName); + Loader form = new Loader(fileName, (e) => AppContext.AppForm.ShowError(e)); DialogResult result = form.ShowDialog(this); if (result == DialogResult.OK) { @@ -273,7 +273,7 @@ namespace WebsitePanel.Installer.Controls { Log.WriteInfo(string.Format("Uninstalling {0}", componentName)); //download installer - Loader form = new Loader(this.AppContext, installer, componentCode, release); + Loader form = new Loader(installer, componentCode, release, (e) => AppContext.AppForm.ShowError(e)); DialogResult result = form.ShowDialog(this); if (result == DialogResult.OK) { @@ -326,15 +326,15 @@ namespace WebsitePanel.Installer.Controls string path = element.GetStringSetting("InstallerPath"); string type = element.GetStringSetting("InstallerType"); string componentId = element.ID; - string componentCode = element.GetStringSetting("ComponentCode"); + string ccode = element.GetStringSetting("ComponentCode"); string componentName = element.GetStringSetting("ComponentName"); - string release = element.GetStringSetting("Release"); + string cversion = element.GetStringSetting("Release"); try { - Log.WriteInfo(string.Format("Setup {0} {1}", componentName, release)); - //download installer - Loader form = new Loader(this.AppContext, installer, componentCode, release); + Log.WriteInfo(string.Format("Setup {0} {1}", componentName, cversion)); + //download installer + Loader form = new Loader(installer, ccode, cversion, (e) => AppContext.AppForm.ShowError(e)); DialogResult result = form.ShowDialog(this); if (result == DialogResult.OK) { diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentsControl.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentsControl.cs index 2cfb8907..218d7520 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentsControl.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/ComponentsControl.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -45,273 +45,273 @@ using WebsitePanel.Installer.Configuration; namespace WebsitePanel.Installer.Controls { - /// - /// Components control - /// - internal partial class ComponentsControl : ResultViewControl - { - delegate void SetGridDataSourceCallback(object dataSource, string dataMember); + /// + /// Components control + /// + internal partial class ComponentsControl : ResultViewControl + { + delegate void SetGridDataSourceCallback(object dataSource, string dataMember); - private string componentCode = null; - private string componentVersion = null; - private string componentSettingsXml = null; + private string componentCode = null; + private string componentVersion = null; + private string componentSettingsXml = null; - public ComponentsControl() - { - InitializeComponent(); - grdComponents.AutoGenerateColumns = false; - } + public ComponentsControl() + { + InitializeComponent(); + grdComponents.AutoGenerateColumns = false; + } - /// - /// Action on click Install link - /// - /// - /// - private void OnInstallLinkClick(object sender, DataGridViewCellEventArgs e) - { - if (e.ColumnIndex == grdComponents.Columns.IndexOf(colLink)) - { - DataRowView row = grdComponents.Rows[e.RowIndex].DataBoundItem as DataRowView; - if (row != null) - { - StartInstaller(row); - StartLoadingComponents(); - } - } - } + /// + /// Action on click Install link + /// + /// + /// + private void OnInstallLinkClick(object sender, DataGridViewCellEventArgs e) + { + if (e.ColumnIndex == grdComponents.Columns.IndexOf(colLink)) + { + DataRowView row = grdComponents.Rows[e.RowIndex].DataBoundItem as DataRowView; + if (row != null) + { + StartInstaller(row); + StartLoadingComponents(); + } + } + else + { - private void StartInstaller(DataRowView row) - { - string applicationName = Utils.GetDbString(row[Global.Parameters.ApplicationName]); - string componentName = Utils.GetDbString(row[Global.Parameters.ComponentName]); - string componentCode = Utils.GetDbString(row[Global.Parameters.ComponentCode]); - string componentDescription = Utils.GetDbString(row[Global.Parameters.ComponentDescription]); - string component = Utils.GetDbString(row[Global.Parameters.Component]); - string version = Utils.GetDbString(row[Global.Parameters.Version]); - string fileName = row[Global.Parameters.FullFilePath].ToString(); - string installerPath = Utils.GetDbString(row[Global.Parameters.InstallerPath]); - string installerType = Utils.GetDbString(row[Global.Parameters.InstallerType]); + } + } - if (CheckForInstalledComponent(componentCode)) - { - AppContext.AppForm.ShowWarning(Global.Messages.ComponentIsAlreadyInstalled); - return; - } - try - { - // download installer - Loader form = new Loader(this.AppContext, fileName); - DialogResult result = form.ShowDialog(this); + private void StartInstaller(DataRowView row) + { + string applicationName = Utils.GetDbString(row[Global.Parameters.ApplicationName]); + string componentName = Utils.GetDbString(row[Global.Parameters.ComponentName]); + string componentCode = Utils.GetDbString(row[Global.Parameters.ComponentCode]); + string componentDescription = Utils.GetDbString(row[Global.Parameters.ComponentDescription]); + string component = Utils.GetDbString(row[Global.Parameters.Component]); + string version = Utils.GetDbString(row[Global.Parameters.Version]); + string fileName = row[Global.Parameters.FullFilePath].ToString(); + string installerPath = Utils.GetDbString(row[Global.Parameters.InstallerPath]); + string installerType = Utils.GetDbString(row[Global.Parameters.InstallerType]); - if (result == DialogResult.OK) - { - string tmpFolder = FileUtils.GetTempDirectory(); - string path = Path.Combine(tmpFolder, installerPath); - Update(); - string method = "Install"; - Log.WriteStart(string.Format("Running installer {0}.{1} from {2}", installerType, method, path)); + if (CheckForInstalledComponent(componentCode)) + { + AppContext.AppForm.ShowWarning(Global.Messages.ComponentIsAlreadyInstalled); + return; + } + try + { + // download installer + Loader form = new Loader(fileName, (e) => AppContext.AppForm.ShowError(e)); + DialogResult result = form.ShowDialog(this); - //prepare installer args - Hashtable args = new Hashtable(); + if (result == DialogResult.OK) + { + string tmpFolder = FileUtils.GetTempDirectory(); + string path = Path.Combine(tmpFolder, installerPath); + Update(); + string method = "Install"; + Log.WriteStart(string.Format("Running installer {0}.{1} from {2}", installerType, method, path)); - args[Global.Parameters.ComponentName] = componentName; - args[Global.Parameters.ApplicationName] = applicationName; - args[Global.Parameters.ComponentCode] = componentCode; - args[Global.Parameters.ComponentDescription] = componentDescription; - args[Global.Parameters.Version] = version; - args[Global.Parameters.InstallerFolder] = tmpFolder; - args[Global.Parameters.InstallerPath] = installerPath; - args[Global.Parameters.InstallerType] = installerType; - args[Global.Parameters.Installer] = Path.GetFileName(fileName); - args[Global.Parameters.ShellVersion] = AssemblyLoader.GetShellVersion(); - args[Global.Parameters.BaseDirectory] = FileUtils.GetCurrentDirectory(); - args[Global.Parameters.ShellMode] = Global.VisualInstallerShell; - args[Global.Parameters.IISVersion] = Global.IISVersion; - args[Global.Parameters.SetupXml] = this.componentSettingsXml; - args[Global.Parameters.ParentForm] = FindForm(); + //prepare installer args + Hashtable args = new Hashtable(); - //run installer - DialogResult res = (DialogResult)AssemblyLoader.Execute(path, installerType, method, new object[] { args }); - Log.WriteInfo(string.Format("Installer returned {0}", res)); - Log.WriteEnd("Installer finished"); - Update(); - if (res == DialogResult.OK) - { - AppContext.AppForm.ReloadApplication(); - } - FileUtils.DeleteTempDirectory(); - } - } - catch (Exception ex) - { - Log.WriteError("Installer error", ex); - AppContext.AppForm.ShowError(ex); - } - finally - { - this.componentSettingsXml = null; - this.componentCode = null; - } + args[Global.Parameters.ComponentName] = componentName; + args[Global.Parameters.ApplicationName] = applicationName; + args[Global.Parameters.ComponentCode] = componentCode; + args[Global.Parameters.ComponentDescription] = componentDescription; + args[Global.Parameters.Version] = version; + args[Global.Parameters.InstallerFolder] = tmpFolder; + args[Global.Parameters.InstallerPath] = installerPath; + args[Global.Parameters.InstallerType] = installerType; + args[Global.Parameters.Installer] = Path.GetFileName(fileName); + args[Global.Parameters.ShellVersion] = AssemblyLoader.GetShellVersion(); + args[Global.Parameters.BaseDirectory] = FileUtils.GetCurrentDirectory(); + args[Global.Parameters.ShellMode] = Global.VisualInstallerShell; + args[Global.Parameters.IISVersion] = Global.IISVersion; + args[Global.Parameters.SetupXml] = this.componentSettingsXml; + args[Global.Parameters.ParentForm] = FindForm(); - } + //run installer + DialogResult res = (DialogResult)AssemblyLoader.Execute(path, installerType, method, new object[] { args }); + Log.WriteInfo(string.Format("Installer returned {0}", res)); + Log.WriteEnd("Installer finished"); + Update(); + if (res == DialogResult.OK) + { + AppContext.AppForm.ReloadApplication(); + } + FileUtils.DeleteTempDirectory(); + } + } + catch (Exception ex) + { + Log.WriteError("Installer error", ex); + AppContext.AppForm.ShowError(ex); + } + finally + { + this.componentSettingsXml = null; + this.componentCode = null; + } - private bool CheckForInstalledComponent(string componentCode) - { - bool ret = false; - List installedComponents = new List(); - foreach (ComponentConfigElement componentConfig in AppConfigManager.AppConfiguration.Components) - { - string code = componentConfig.Settings["ComponentCode"].Value; - installedComponents.Add(code); - if (code == componentCode) - { - ret = true; - break; - } - } - if (componentCode == "standalone") - { - if (installedComponents.Contains("server") || - installedComponents.Contains("enterprise server") || - installedComponents.Contains("portal")) - ret = true; - } - return ret; - } + } - /// - /// Displays component description when entering grid row - /// - /// - /// - private void OnRowEnter(object sender, DataGridViewCellEventArgs e) - { - DataRowView row = grdComponents.Rows[e.RowIndex].DataBoundItem as DataRowView; - if (row != null) - { - lblDescription.Text = Utils.GetDbString(row["ComponentDescription"]); - } - } + private bool CheckForInstalledComponent(string componentCode) + { + bool ret = false; + List installedComponents = new List(); + foreach (ComponentConfigElement componentConfig in AppConfigManager.AppConfiguration.Components) + { + string code = componentConfig.Settings["ComponentCode"].Value; + installedComponents.Add(code); + if (code == componentCode) + { + ret = true; + break; + } + } + if (componentCode == "standalone") + { + if (installedComponents.Contains("server") || + installedComponents.Contains("enterprise server") || + installedComponents.Contains("portal")) + ret = true; + } + return ret; + } - /// - /// Start new thread to load components - /// - /// - /// - private void OnLoadComponentsClick(object sender, EventArgs e) - { - StartLoadingComponents(); - } + /// + /// Displays component description when entering grid row + /// + /// + /// + private void OnRowEnter(object sender, DataGridViewCellEventArgs e) + { + DataRowView row = grdComponents.Rows[e.RowIndex].DataBoundItem as DataRowView; + if (row != null) + { + lblDescription.Text = Utils.GetDbString(row["ComponentDescription"]); + } + } - private void StartLoadingComponents() - { - //load list of available components in the separate thread - AppContext.AppForm.StartAsyncProgress("Connecting...", true); - ThreadStart threadDelegate = new ThreadStart(LoadComponents); - Thread newThread = new Thread(threadDelegate); - newThread.Start(); - } + /// + /// Start new thread to load components + /// + /// + /// + private void OnLoadComponentsClick(object sender, EventArgs e) + { + StartLoadingComponents(); + } - /// - /// Loads list of available components via web service - /// - private void LoadComponents() - { - try - { - Log.WriteStart("Loading list of available components"); - lblDescription.Text = string.Empty; - //load components via web service - var webService = ServiceProviderProxy.GetInstallerWebService(); - DataSet dsComponents = webService.GetAvailableComponents(); - //remove already installed components - foreach (DataRow row in dsComponents.Tables[0].Rows) - { - string componentCode = Utils.GetDbString(row["ComponentCode"]); - if (CheckForInstalledComponent(componentCode)) - { - row.Delete(); - } - } - dsComponents.AcceptChanges(); - Log.WriteEnd("Available components loaded"); - SetGridDataSource(dsComponents, dsComponents.Tables[0].TableName); - AppContext.AppForm.FinishProgress(); - } - catch (Exception ex) - { - Log.WriteError("Web service error", ex); - AppContext.AppForm.FinishProgress(); - AppContext.AppForm.ShowServerError(); - } - } + private void StartLoadingComponents() + { + //load list of available components in the separate thread + AppContext.AppForm.StartAsyncProgress("Connecting...", true); + ThreadPool.QueueUserWorkItem(o => LoadComponents()); + } - /// - /// Thread safe grid binding. - /// - /// Data source - /// Data member - private void SetGridDataSource(object dataSource, string dataMember) - { - // InvokeRequired required compares the thread ID of the - // calling thread to the thread ID of the creating thread. - // If these threads are different, it returns true. - if (this.grdComponents.InvokeRequired) - { - SetGridDataSourceCallback callBack = new SetGridDataSourceCallback(SetGridDataSource); - this.grdComponents.Invoke(callBack, new object[] { dataSource, dataMember }); - } - else - { - this.grdComponents.DataSource = dataSource; - this.grdComponents.DataMember = dataMember; - } - } + /// + /// Loads list of available components via web service + /// + private void LoadComponents() + { + try + { + Log.WriteStart("Loading list of available components"); + lblDescription.Text = string.Empty; + //load components via web service + var webService = ServiceProviderProxy.GetInstallerWebService(); + DataSet dsComponents = webService.GetAvailableComponents(); + //remove already installed components + foreach (DataRow row in dsComponents.Tables[0].Rows) + { + string componentCode = Utils.GetDbString(row["ComponentCode"]); + if (CheckForInstalledComponent(componentCode)) + { + row.Delete(); + } + } + dsComponents.AcceptChanges(); + Log.WriteEnd("Available components loaded"); + SetGridDataSource(dsComponents, dsComponents.Tables[0].TableName); + AppContext.AppForm.FinishProgress(); + } + catch (Exception ex) + { + Log.WriteError("Web service error", ex); + AppContext.AppForm.FinishProgress(); + AppContext.AppForm.ShowServerError(); + } + } - /// - /// Installs component during unattended setup - /// - /// - internal void InstallComponent(string componentCode, string componentVersion, string settingsXml) - { - //load list of available components in the separate thread - this.componentCode = componentCode; - this.componentVersion = componentVersion; - this.componentSettingsXml = settingsXml; - AppContext.AppForm.StartAsyncProgress("Connecting...", true); - ThreadStart threadDelegate = new ThreadStart(Install); - Thread newThread = new Thread(threadDelegate); - newThread.Start(); - } + /// + /// Thread safe grid binding. + /// + /// Data source + /// Data member + private void SetGridDataSource(object dataSource, string dataMember) + { + // InvokeRequired required compares the thread ID of the + // calling thread to the thread ID of the creating thread. + // If these threads are different, it returns true. + if (this.grdComponents.InvokeRequired) + { + SetGridDataSourceCallback callBack = new SetGridDataSourceCallback(SetGridDataSource); + this.grdComponents.Invoke(callBack, new object[] { dataSource, dataMember }); + } + else + { + this.grdComponents.DataSource = dataSource; + this.grdComponents.DataMember = dataMember; + } + } - /// - /// Loads list of available components via web service and install specified component - /// during unattended setup - /// - private void Install() - { - LoadComponents(); - foreach (DataGridViewRow gridRow in grdComponents.Rows) - { - DataRowView row = gridRow.DataBoundItem as DataRowView; - if (row != null) - { - string code = Utils.GetDbString(row["ComponentCode"]); - string version = Utils.GetDbString(row["Version"]); - if (code == componentCode) - { - //check component version if specified - if (!string.IsNullOrEmpty(componentVersion)) - { - if (version != componentVersion) - continue; - } - StartInstaller(row); - AppContext.AppForm.ProceedUnattendedSetup(); - break; - } - } - } - } - } + /// + /// Installs component during unattended setup + /// + /// + internal void InstallComponent(string componentCode, string componentVersion, string settingsXml) + { + //load list of available components in the separate thread + this.componentCode = componentCode; + this.componentVersion = componentVersion; + this.componentSettingsXml = settingsXml; + AppContext.AppForm.StartAsyncProgress("Connecting...", true); + ThreadPool.QueueUserWorkItem(o => Install()); + } + + /// + /// Loads list of available components via web service and install specified component + /// during unattended setup + /// + private void Install() + { + LoadComponents(); + foreach (DataGridViewRow gridRow in grdComponents.Rows) + { + DataRowView row = gridRow.DataBoundItem as DataRowView; + if (row != null) + { + string code = Utils.GetDbString(row["ComponentCode"]); + string version = Utils.GetDbString(row["Version"]); + if (code == componentCode) + { + //check component version if specified + if (!string.IsNullOrEmpty(componentVersion)) + { + if (version != componentVersion) + continue; + } + StartInstaller(row); + AppContext.AppForm.ProceedUnattendedSetup(); + break; + } + } + } + } + } } diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/Loader.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/Loader.cs index 81eeab02..8fd6558b 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/Loader.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Controls/Loader.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -39,118 +39,156 @@ using Ionic.Zip; using WebsitePanel.Installer.Services; using WebsitePanel.Installer.Common; +using WebsitePanel.Installer.Core; namespace WebsitePanel.Installer.Controls { - public delegate void OperationProgressDelegate(int percentage); + public delegate void OperationProgressDelegate(int percentage); - /// - /// Loader form. - /// - internal partial class Loader : Form - { - private AppContext appContext; - private Core.Loader appLoader; + /// + /// Loader form. + /// + internal partial class Loader : Form + { + private Core.Loader appLoader; - public Loader() - { - InitializeComponent(); - DialogResult = DialogResult.Cancel; - } + public Loader() + { + InitializeComponent(); + DialogResult = DialogResult.Cancel; + } - public Loader(AppContext context, string remoteFile) - : this() - { - this.appContext = context; - // - appLoader = new Core.Loader(remoteFile); - // - Start(); - } + public Loader(string remoteFile, Action callback) + : this() + { + Start(remoteFile, callback); + } - public Loader(AppContext context, string localFile, string componentCode, string version) - : this() - { - this.appContext = context; - // - appLoader = new Core.Loader(localFile, componentCode, version); - // - Start(); - } + public Loader(string localFile, string componentCode, string version, Action callback) + : this() + { + Start(componentCode, version, callback); + } - private void Start() - { - // - appLoader.OperationFailed += new EventHandler>(appLoader_OperationFailed); - appLoader.ProgressChanged += new EventHandler>(appLoader_ProgressChanged); - appLoader.StatusChanged += new EventHandler>(appLoader_StatusChanged); - appLoader.OperationCompleted += new EventHandler(appLoader_OperationCompleted); - // - appLoader.LoadAppDistributive(); - } + /// + /// Resolves URL of the component's distributive and initiates download process. + /// + /// Component code to resolve + /// Component version to resolve + private void Start(string componentCode, string version, Action callback) + { + string remoteFile = Utils.GetDistributiveLocationInfo(componentCode, version); - void appLoader_OperationCompleted(object sender, EventArgs e) - { - DialogResult = DialogResult.OK; - Close(); - } + Start(remoteFile, callback); + } - void appLoader_StatusChanged(object sender, Core.LoaderEventArgs e) - { - lblProcess.Text = e.StatusMessage; - lblValue.Text = e.EventData; - // Adjust Cancel button availability for an operation being performed - if (btnCancel.Enabled != e.Cancellable) - { - btnCancel.Enabled = e.Cancellable; - } - // This check allows to avoid extra form redrawing operations - if (ControlBox != e.Cancellable) - { - ControlBox = e.Cancellable; - } - } + /// + /// Initializes and starts the app distributive download process. + /// + /// URL of the file to be downloaded + private void Start(string remoteFile, Action callback) + { + appLoader = Core.LoaderFactory.CreateFileLoader(remoteFile); - void appLoader_ProgressChanged(object sender, Core.LoaderEventArgs e) - { - progressBar.Value = e.EventData; - // Adjust Cancel button availability for an operation being performed - if (btnCancel.Enabled != e.Cancellable) - { - btnCancel.Enabled = e.Cancellable; - } - // This check allows to avoid extra form redrawing operations - if (ControlBox != e.Cancellable) - { - ControlBox = e.Cancellable; - } - } + appLoader.OperationFailed += new EventHandler>(appLoader_OperationFailed); + appLoader.OperationFailed += (object sender, Core.LoaderEventArgs e) => { + if (callback != null) + { + try + { + callback(e.EventData); + } + catch + { + // Just swallow the exception as we have no interest in it. + } + } + }; + appLoader.ProgressChanged += new EventHandler>(appLoader_ProgressChanged); + appLoader.StatusChanged += new EventHandler>(appLoader_StatusChanged); + appLoader.OperationCompleted += new EventHandler(appLoader_OperationCompleted); - void appLoader_OperationFailed(object sender, Core.LoaderEventArgs e) - { - appContext.AppForm.ShowError(e.EventData); - // - DialogResult = DialogResult.Abort; - Close(); - } + appLoader.LoadAppDistributive(); + } - private void btnCancel_Click(object sender, EventArgs e) - { - Log.WriteInfo("Execution was canceled by user"); - Close(); - } + void appLoader_OperationCompleted(object sender, EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } - private void OnLoaderFormClosing(object sender, FormClosingEventArgs e) - { - if (this.DialogResult == DialogResult.Cancel) - { - appLoader.AbortOperation(); - } - // Remove event handlers - appLoader.OperationFailed -= new EventHandler>(appLoader_OperationFailed); - appLoader.ProgressChanged -= new EventHandler>(appLoader_ProgressChanged); - appLoader.StatusChanged -= new EventHandler>(appLoader_StatusChanged); - appLoader.OperationCompleted -= new EventHandler(appLoader_OperationCompleted); - } - } + void appLoader_StatusChanged(object sender, Core.LoaderEventArgs e) + { + lblProcess.Text = e.StatusMessage; + lblValue.Text = e.EventData; + // Adjust Cancel button availability for an operation being performed + if (btnCancel.Enabled != e.Cancellable) + { + btnCancel.Enabled = e.Cancellable; + } + // This check allows to avoid extra form redrawing operations + if (ControlBox != e.Cancellable) + { + ControlBox = e.Cancellable; + } + } + + void appLoader_ProgressChanged(object sender, Core.LoaderEventArgs e) + { + bool updateControl = (progressBar.Value != e.EventData); + progressBar.Value = e.EventData; + // Adjust Cancel button availability for an operation being performed + if (btnCancel.Enabled != e.Cancellable) + { + btnCancel.Enabled = e.Cancellable; + } + // This check allows to avoid extra form redrawing operations + if (ControlBox != e.Cancellable) + { + ControlBox = e.Cancellable; + } + // + if (updateControl) + { + progressBar.Update(); + } + } + + void appLoader_OperationFailed(object sender, Core.LoaderEventArgs e) + { + DialogResult = DialogResult.Abort; + Close(); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + DetachEventHandlers(); + Log.WriteInfo("Execution was canceled by user"); + Close(); + } + + private void DetachEventHandlers() + { + // Detach event handlers + if (appLoader != null) + { + appLoader.OperationFailed -= new EventHandler>(appLoader_OperationFailed); + appLoader.ProgressChanged -= new EventHandler>(appLoader_ProgressChanged); + appLoader.StatusChanged -= new EventHandler>(appLoader_StatusChanged); + appLoader.OperationCompleted -= new EventHandler(appLoader_OperationCompleted); + } + } + + private void OnLoaderFormClosing(object sender, FormClosingEventArgs e) + { + if (this.DialogResult == DialogResult.Cancel) + { + if (appLoader != null) + { + appLoader.AbortOperation(); + appLoader = null; + } + } + } + } } \ No newline at end of file diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Updater.exe b/WebsitePanel.Installer/Sources/WebsitePanel.Installer/Updater.exe index b3fb1974bb37500dc3e5a856fe2f9da322cfd2cb..92ddc22630d1e9da6330d7e5999a9c0563d452f7 100644 GIT binary patch delta 36433 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 delta 36464 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 diff --git a/WebsitePanel.Installer/Sources/WebsitePanel.Setup/Wizard/ExpressInstallPage.cs b/WebsitePanel.Installer/Sources/WebsitePanel.Setup/Wizard/ExpressInstallPage.cs index 45447e4f..f101d6da 100644 --- a/WebsitePanel.Installer/Sources/WebsitePanel.Setup/Wizard/ExpressInstallPage.cs +++ b/WebsitePanel.Installer/Sources/WebsitePanel.Setup/Wizard/ExpressInstallPage.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Outercurve Foundation. +// Copyright (c) 2012, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -1901,52 +1901,52 @@ namespace WebsitePanel.Setup } } - private void UpdateWebConfigNamespaces() - { - try - { - // find all .config files in the installation directory - string[] configFiles = Directory.GetFiles(Wizard.SetupVariables.InstallationFolder, - "*.config", SearchOption.TopDirectoryOnly); + private void UpdateWebConfigNamespaces() + { + try + { + // find all .config files in the installation directory + string[] configFiles = Directory.GetFiles(Wizard.SetupVariables.InstallationFolder, + "*.config", SearchOption.TopDirectoryOnly); - if (configFiles != null && configFiles.Length > 0) - { - foreach (string path in configFiles) - { - try - { - Log.WriteStart(String.Format("Updating '{0}' file", path)); + if (configFiles != null && configFiles.Length > 0) + { + foreach (string path in configFiles) + { + try + { + Log.WriteStart(String.Format("Updating '{0}' file", path)); - // load configuration file in memory - string content = File.ReadAllText(path); + // load configuration file in memory + string content = File.ReadAllText(path); - // replace DotNetPark. to empty strings - content = Regex.Replace(content, "dotnetpark\\.", "", RegexOptions.IgnoreCase); + // replace DotNetPark. to empty strings + content = Regex.Replace(content, "dotnetpark\\.", "", RegexOptions.IgnoreCase); - // save updated config - File.WriteAllText(path, content); + // save updated config + File.WriteAllText(path, content); - Log.WriteEnd(String.Format("Updated '{0}' file", path)); - InstallLog.AppendLine(String.Format("- Updated {0} file", path)); - } - catch (Exception ex) - { - if (Utils.IsThreadAbortException(ex)) - return; - Log.WriteError(String.Format("Error updating '{0}' file", path), ex); - throw; - } - } - } - } - catch (Exception ex) - { - if (Utils.IsThreadAbortException(ex)) - return; - Log.WriteError("Error listing *.config files", ex); - throw; - } - } + Log.WriteEnd(String.Format("Updated '{0}' file", path)); + InstallLog.AppendLine(String.Format("- Updated {0} file", path)); + } + catch (Exception ex) + { + if (Utils.IsThreadAbortException(ex)) + return; + Log.WriteError(String.Format("Error updating '{0}' file", path), ex); + throw; + } + } + } + } + catch (Exception ex) + { + if (Utils.IsThreadAbortException(ex)) + return; + Log.WriteError("Error listing *.config files", ex); + throw; + } + } private void UpdateEnterpriseServerUrl() {