diff --git a/CFUValidator/Validators/ZipContentsValidator.cs b/CFUValidator/Validators/ZipContentsValidator.cs index b619e05a9..85f20537a 100644 --- a/CFUValidator/Validators/ZipContentsValidator.cs +++ b/CFUValidator/Validators/ZipContentsValidator.cs @@ -79,10 +79,8 @@ namespace CFUValidator.Validators return; } - string tempFileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - var action = new DownloadAndUnzipXenServerPatchAction(patch.Patch.Name, new Uri(patch.Patch.PatchUrl), - tempFileName, false, BrandManager.ExtensionUpdate, "iso"); + var action = new DownloadAndUnzipUpdateAction(patch.Patch.Name, new Uri(patch.Patch.PatchUrl), + BrandManager.ExtensionUpdate, "iso"); try { diff --git a/XenAdmin/Wizards/PatchingWizard/PlanActions/DownloadPatchPlanAction.cs b/XenAdmin/Wizards/PatchingWizard/PlanActions/DownloadPatchPlanAction.cs index 6b43a4810..2defe2a60 100644 --- a/XenAdmin/Wizards/PatchingWizard/PlanActions/DownloadPatchPlanAction.cs +++ b/XenAdmin/Wizards/PatchingWizard/PlanActions/DownloadPatchPlanAction.cs @@ -45,7 +45,6 @@ namespace XenAdmin.Wizards.PatchingWizard.PlanActions private readonly XenServerPatch patch; private Dictionary AllDownloadedPatches = new Dictionary(); private KeyValuePair patchFromDisk; - private string tempFileName = null; public DownloadPatchPlanAction(IXenConnection connection, XenServerPatch patch, Dictionary allDownloadedPatches, KeyValuePair patchFromDisk) : base(connection) @@ -96,18 +95,17 @@ namespace XenAdmin.Wizards.PatchingWizard.PlanActions return; Uri address = new Uri(patchUri); - tempFileName = Path.GetTempFileName(); var exts = Helpers.ElyOrGreater(Connection) ? "iso" : BrandManager.ExtensionUpdate; - var downloadAction = new DownloadAndUnzipXenServerPatchAction(patch.Name, address, tempFileName, true, exts); + var downloadAction = new DownloadAndUnzipUpdateAction(patch.Name, address, exts); - downloadAction.Changed += downloadAndUnzipXenServerPatchAction_Changed; - downloadAction.Completed += downloadAndUnzipXenServerPatchAction_Completed; + downloadAction.Changed += DownloadAction_Changed; + downloadAction.Completed += DownloadAction_Completed; downloadAction.RunSync(session); } - private void downloadAndUnzipXenServerPatchAction_Changed(ActionBase action) + private void DownloadAction_Changed(ActionBase action) { if (action == null) return; @@ -124,17 +122,16 @@ namespace XenAdmin.Wizards.PatchingWizard.PlanActions } - private void downloadAndUnzipXenServerPatchAction_Completed(ActionBase sender) + private void DownloadAction_Completed(ActionBase sender) { - var action = sender as DownloadAndUnzipXenServerPatchAction; - if (action == null) + if (!(sender is DownloadAndUnzipUpdateAction action)) return; - action.Changed -= downloadAndUnzipXenServerPatchAction_Changed; - action.Completed -= downloadAndUnzipXenServerPatchAction_Completed; + action.Changed -= DownloadAction_Changed; + action.Completed -= DownloadAction_Completed; if (action.Succeeded) - AllDownloadedPatches[patch] = action.PatchPath; + AllDownloadedPatches[patch] = action.UpdatePath; } } } diff --git a/XenAdmin/Wizards/WizardHelpers.cs b/XenAdmin/Wizards/WizardHelpers.cs index 3b65a867a..80b3eb912 100644 --- a/XenAdmin/Wizards/WizardHelpers.cs +++ b/XenAdmin/Wizards/WizardHelpers.cs @@ -123,15 +123,14 @@ namespace XenAdmin.Wizards if (string.IsNullOrEmpty(zippedUpdatePath)) return null; - var unzipAction = new DownloadAndUnzipXenServerPatchAction(Path.GetFileNameWithoutExtension(zippedUpdatePath), - null, zippedUpdatePath, true, BrandManager.ExtensionUpdate, "iso"); + var unzipAction = new UnzipUpdateAction(zippedUpdatePath, BrandManager.ExtensionUpdate, "iso"); using (var dlg = new ActionProgressDialog(unzipAction, ProgressBarStyle.Marquee)) { dlg.ShowDialog(control); } - return !string.IsNullOrEmpty(unzipAction.PatchPath) ? unzipAction.PatchPath : null; + return !string.IsNullOrEmpty(unzipAction.UpdatePath) ? unzipAction.UpdatePath : null; } public static bool IsValidFile(string fileName, out string failureReason) diff --git a/XenModel/Actions/Updates/DownloadAndUnzipXenServerPatchAction.cs b/XenModel/Actions/Updates/DownloadAndUnzipXenServerPatchAction.cs deleted file mode 100644 index 6eff0bd49..000000000 --- a/XenModel/Actions/Updates/DownloadAndUnzipXenServerPatchAction.cs +++ /dev/null @@ -1,356 +0,0 @@ -/* Copyright (c) Cloud Software Group, Inc. - * - * Redistribution and use in source and binary forms, - * with or without modification, are permitted provided - * that the following conditions are met: - * - * * Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other - * materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -using System; -using System.Net; -using System.ComponentModel; -using System.Threading; -using System.IO; -using System.Linq; -using System.Net.NetworkInformation; -using XenCenterLib.Archive; - -namespace XenAdmin.Actions -{ - internal enum DownloadState - { - InProgress, - Cancelled, - Completed, - Error - }; - - public class DownloadAndUnzipXenServerPatchAction : AsyncAction, IByteProgressAction - { - private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - - private const int SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS = 900; - private const int SLEEP_TIME_BEFORE_RETRY_MS = 5000; - - //If you consider increasing this for any reason (I think 5 is already more than enough), have a look at the usage of SLEEP_TIME_BEFORE_RETRY_MS in DownloadFile() as well. - private const int MAX_NUMBER_OF_TRIES = 5; - - private readonly Uri address; - private readonly string outputFileName; - private readonly string updateName; - private readonly string[] updateFileSuffixes; - private readonly bool downloadUpdate; - private readonly bool skipUnzipping; - private DownloadState patchDownloadState; - private Exception patchDownloadError; - - public string PatchPath { get; private set; } - - public string ByteProgressDescription { get; set; } - - public DownloadAndUnzipXenServerPatchAction(string patchName, Uri uri, string outputFileName, bool suppressHist, - params string[] updateFileExtensions) - : base(null, uri == null - ? string.Format(Messages.UPDATES_WIZARD_EXTRACT_ACTION_TITLE, patchName) - : string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_TITLE, patchName), string.Empty, suppressHist) - { - updateName = patchName; - address = uri; - downloadUpdate = address != null; - updateFileSuffixes = (from item in updateFileExtensions select $".{item}").ToArray(); - skipUnzipping = downloadUpdate && updateFileSuffixes.Any(item => address.ToString().ToLowerInvariant().Contains(item.ToLowerInvariant())); - this.outputFileName = outputFileName; - } - - private WebClient client; - - private void DownloadFile() - { - int errorCount = 0; - bool needToRetry = false; - - client = new WebClient(); - //register download events - client.DownloadProgressChanged += client_DownloadProgressChanged; - client.DownloadFileCompleted += client_DownloadFileCompleted; - - // register event handler to detect changes in network connectivity - NetworkChange.NetworkAvailabilityChanged += NetworkAvailabilityChanged; - - try - { - do - { - if (needToRetry) - Thread.Sleep(SLEEP_TIME_BEFORE_RETRY_MS); - - needToRetry = false; - - client.Proxy = XenAdminConfigManager.Provider.GetProxyFromSettings(null, false); - - //start the download - patchDownloadState = DownloadState.InProgress; - client.DownloadFileAsync(address, outputFileName); - - bool patchDownloadCancelling = false; - - //wait for the file to be downloaded - while (patchDownloadState == DownloadState.InProgress) - { - if (!patchDownloadCancelling && (Cancelling || Cancelled)) - { - Description = Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOAD_CANCELLED_DESC; - client.CancelAsync(); - patchDownloadCancelling = true; - } - - Thread.Sleep(SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS); - } - - if (patchDownloadState == DownloadState.Cancelled) - throw new CancelledException(); - - if (patchDownloadState == DownloadState.Error) - { - needToRetry = true; - - // this many errors so far - including this one - errorCount++; - - // logging only, it will retry again. - log.ErrorFormat( - "Error while downloading from '{0}'. Number of errors so far (including this): {1}. Trying maximum {2} times.", - address, errorCount, MAX_NUMBER_OF_TRIES); - - if (patchDownloadError == null) - log.Error("An unknown error occurred."); - else - log.Error(patchDownloadError); - } - } while (errorCount < MAX_NUMBER_OF_TRIES && needToRetry); - } - finally - { - //deregister download events - client.DownloadProgressChanged -= client_DownloadProgressChanged; - client.DownloadFileCompleted -= client_DownloadFileCompleted; - - NetworkChange.NetworkAvailabilityChanged -= NetworkAvailabilityChanged; - - client.Dispose(); - } - - //if this is still the case after having retried MAX_NUMBER_OF_TRIES number of times. - if (patchDownloadState == DownloadState.Error) - { - log.ErrorFormat("Giving up - Maximum number of retries ({0}) has been reached.", MAX_NUMBER_OF_TRIES); - - MarkCompleted(patchDownloadError ?? new Exception(Messages.ERROR_UNKNOWN)); - } - - } - - private void NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) - { - if (!e.IsAvailable && client != null && patchDownloadState == DownloadState.InProgress) - { - patchDownloadError = new WebException(Messages.NETWORK_CONNECTIVITY_ERROR); - patchDownloadState = DownloadState.Error; - client.CancelAsync(); - } - } - - private void ExtractFile() - { - ArchiveIterator iterator = null; - ZipArchiveIterator zipIterator = null; - - try - { - using (Stream stream = new FileStream(outputFileName, FileMode.Open, FileAccess.Read)) - { - iterator = ArchiveFactory.Reader(ArchiveFactory.Type.Zip, stream); - zipIterator = iterator as ZipArchiveIterator; - - if (zipIterator != null) - zipIterator.CurrentFileExtracted += archiveIterator_CurrentFileExtracted; - - while (iterator.HasNext()) - { - string currentExtension = Path.GetExtension(iterator.CurrentFileName()); - - if (updateFileSuffixes.Any(item => item.ToLowerInvariant() == currentExtension.ToLowerInvariant())) - { - string path = downloadUpdate - ? Path.Combine(Path.GetDirectoryName(outputFileName), iterator.CurrentFileName()) - : Path.Combine(Path.GetTempPath(), iterator.CurrentFileName()); - - log.InfoFormat( - "Found '{0}' in the downloaded archive when looking for a '{1}' file. Extracting...", - iterator.CurrentFileName(), currentExtension); - - using (Stream outputStream = new FileStream(path, FileMode.Create)) - { - iterator.ExtractCurrentFile(outputStream, null); - PatchPath = path; - - log.InfoFormat("Update file extracted to '{0}'", path); - break; - } - } - } - } - } - catch (Exception e) - { - log.Error("Exception occurred when extracting downloaded archive.", e); - throw new Exception(Messages.DOWNLOAD_AND_EXTRACT_ACTION_EXTRACTING_ERROR); - } - finally - { - if (zipIterator != null) - zipIterator.CurrentFileExtracted -= archiveIterator_CurrentFileExtracted; - - if (iterator != null) - iterator.Dispose(); - - if (downloadUpdate) - { - try { File.Delete(outputFileName); } - catch - { - // ignored - } - } - } - - if (string.IsNullOrEmpty(PatchPath) && downloadUpdate) - { - MarkCompleted(new Exception(Messages.DOWNLOAD_AND_EXTRACT_ACTION_FILE_NOT_FOUND)); - log.InfoFormat( - "The downloaded archive does not contain a file with any of the following extensions: {0}", - string.Join(", ", updateFileSuffixes)); - } - } - - protected override void Run() - { - if (downloadUpdate) - { - log.InfoFormat("Downloading update '{0}' (from '{1}') to '{2}'", updateName, address, outputFileName); - Description = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DESC, updateName); - LogDescriptionChanges = false; - DownloadFile(); - LogDescriptionChanges = true; - - if (IsCompleted || Cancelled) - return; - - if (Cancelling) - throw new CancelledException(); - } - - if (skipUnzipping) - { - try - { - string newFilePath = Path.Combine(Path.GetDirectoryName(outputFileName), updateName); - if (File.Exists(newFilePath)) - File.Delete(newFilePath); - File.Move(outputFileName, newFilePath); - PatchPath = newFilePath; - log.DebugFormat("XenServer patch '{0}' is ready", updateName); - } - catch (Exception e) - { - log.Error("Exception occurred when preparing archive.", e); - throw; - } - } - else - { - log.DebugFormat("Extracting XenServer patch '{0}'", updateName); - Description = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_EXTRACTING_DESC, updateName); - ExtractFile(); - log.DebugFormat("Extracting XenServer patch '{0}' completed", updateName); - } - - Description = Messages.COMPLETED; - MarkCompleted(); - } - - private void archiveIterator_CurrentFileExtracted(int fileIndex, int totalFileCount) - { - PercentComplete = downloadUpdate - ? 95 + (int)(5.0 * fileIndex / totalFileCount) - : (int)(100.0 * fileIndex / totalFileCount); - } - - void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) - { - int pc = (int)(95.0 * e.BytesReceived / e.TotalBytesToReceive); - var descr = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DETAILS_DESC, updateName, - Util.DiskSizeString(e.BytesReceived, "F1"), - Util.DiskSizeString(e.TotalBytesToReceive)); - ByteProgressDescription = descr; - Tick(pc, descr); - } - - void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) - { - if (e.Cancelled && patchDownloadState == DownloadState.Error) // cancelled due to network connectivity issue (see NetworkAvailabilityChanged) - return; - - if (e.Cancelled) //user cancelled - { - patchDownloadState = DownloadState.Cancelled; - log.DebugFormat("XenServer patch '{0}' download cancelled by the user", updateName); - return; - } - - if (e.Error != null) //failure - { - patchDownloadError = e.Error; - log.DebugFormat("XenServer patch '{0}' download failed", updateName); - patchDownloadState = DownloadState.Error; - return; - } - - //success - patchDownloadState = DownloadState.Completed; - log.DebugFormat("XenServer patch '{0}' download completed successfully", updateName); - } - - public override void RecomputeCanCancel() - { - CanCancel = !Cancelling && !IsCompleted && (patchDownloadState == DownloadState.InProgress); - } - - protected override void CancelRelatedTask() - { - } - - } -} diff --git a/XenModel/Actions/Updates/DownloadUnzipUpdateAction.cs b/XenModel/Actions/Updates/DownloadUnzipUpdateAction.cs new file mode 100644 index 000000000..a9842e422 --- /dev/null +++ b/XenModel/Actions/Updates/DownloadUnzipUpdateAction.cs @@ -0,0 +1,418 @@ +/* Copyright (c) Cloud Software Group, Inc. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +using System; +using System.Net; +using System.ComponentModel; +using System.Threading; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using XenCenterLib.Archive; +using XenAdmin.Actions.Updates; + +namespace XenAdmin.Actions +{ + internal enum DownloadState + { + InProgress, + Cancelled, + Completed, + Error + } + + public abstract class DownloadUnzipUpdateAction : AsyncAction + { + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + protected readonly string UpdateName; + protected readonly string[] updateFileSuffixes; + + public string UpdatePath { get; protected set; } + + public string ByteProgressDescription { get; set; } + + protected DownloadUnzipUpdateAction(string updateName, params string[] updateFileExtensions) + : base(null, string.Empty, string.Empty, true) + { + UpdateName = updateName; + updateFileSuffixes = (from item in updateFileExtensions select $".{item}").ToArray(); + } + + protected string ExtractFile(string zippedFilePath, bool deleteOriginal) + { + log.DebugFormat("Extracting XenServer update '{0}'", UpdateName); + Description = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_EXTRACTING_DESC, UpdateName); + + ArchiveIterator iterator = null; + ZipArchiveIterator zipIterator = null; + + try + { + using (Stream stream = new FileStream(zippedFilePath, FileMode.Open, FileAccess.Read)) + { + iterator = ArchiveFactory.Reader(ArchiveFactory.Type.Zip, stream); + zipIterator = iterator as ZipArchiveIterator; + + if (zipIterator != null) + zipIterator.CurrentFileExtracted += UpdateExtractionProgress; + + while (iterator.HasNext()) + { + string currentExtension = Path.GetExtension(iterator.CurrentFileName()); + + if (updateFileSuffixes.Any(item => item.ToLowerInvariant() == currentExtension.ToLowerInvariant())) + { + string path = Path.Combine(Path.GetTempPath(), iterator.CurrentFileName()); + + log.InfoFormat( + "Found '{0}' in the downloaded archive when looking for a '{1}' file. Extracting...", + iterator.CurrentFileName(), currentExtension); + + using (Stream outputStream = new FileStream(path, FileMode.Create)) + { + iterator.ExtractCurrentFile(outputStream, null); + log.InfoFormat("Update file extracted to '{0}'", path); + return path; + } + } + } + } + + return null; + } + catch (Exception e) + { + log.Error("Exception occurred when extracting downloaded archive.", e); + throw new Exception(Messages.DOWNLOAD_AND_EXTRACT_ACTION_EXTRACTING_ERROR); + } + finally + { + if (zipIterator != null) + zipIterator.CurrentFileExtracted -= UpdateExtractionProgress; + + iterator?.Dispose(); + + if (deleteOriginal) + { + try + { + File.Delete(zippedFilePath); + } + catch + { + // ignored + } + } + + log.DebugFormat("Extracting XenServer update '{0}' completed", UpdateName); + } + } + + protected override void CancelRelatedTask() + { + } + + protected abstract void UpdateExtractionProgress(int fileIndex, int totalFileCount); + } + + + public class DownloadAndUnzipUpdateAction : DownloadUnzipUpdateAction, IByteProgressAction + { + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// If you consider increasing this for any reason (I think 5 is already more than enough), + /// have a look at the usage of SLEEP_TIME_BEFORE_RETRY_MS in DownloadFile() as well. + /// + private const int MAX_NUMBER_OF_TRIES = 5; + + private const int SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS = 900; + private const int SLEEP_TIME_BEFORE_RETRY_MS = 5000; + + private WebClient _client; + private readonly Uri _updateUri; + private readonly bool _skipUnzipping; + private DownloadState _updateDownloadState; + private Exception _updateDownloadError; + + public DownloadAndUnzipUpdateAction(string updateName, Uri uri, params string[] updateFileExtensions) + : base(updateName, updateFileExtensions) + { + Title = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_TITLE, updateName); + _updateUri = uri; + _skipUnzipping = updateFileSuffixes.Any(item => uri.ToString().ToLowerInvariant().Contains(item.ToLowerInvariant())); + } + + protected override void Run() + { + DownloadFile(out var localPath); + + if (IsCompleted || Cancelled) + return; + + if (Cancelling) + throw new CancelledException(); + + if (_skipUnzipping) + { + try + { + string newFilePath = Path.Combine(Path.GetDirectoryName(localPath), UpdateName); + if (File.Exists(newFilePath)) + File.Delete(newFilePath); + File.Move(localPath, newFilePath); + UpdatePath = newFilePath; + log.DebugFormat("XenServer update '{0}' is ready", UpdateName); + } + catch (Exception e) + { + log.Error("Exception occurred when preparing archive.", e); + throw; + } + } + else + { + UpdatePath = ExtractFile(localPath, true); + } + + if (string.IsNullOrEmpty(UpdatePath)) + { + log.InfoFormat( + "The downloaded archive does not contain a file with any of the following extensions: {0}", + string.Join(", ", updateFileSuffixes)); + throw new Exception(Messages.DOWNLOAD_AND_EXTRACT_ACTION_FILE_NOT_FOUND); + } + + Description = Messages.COMPLETED; + } + + private void DownloadFile(out string outputFileName) + { + outputFileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + log.InfoFormat("Downloading update '{0}' (from '{1}') to '{2}'", UpdateName, _updateUri, outputFileName); + Description = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DESC, UpdateName); + LogDescriptionChanges = false; + + _client = new WebClient(); + _client.DownloadProgressChanged += client_DownloadProgressChanged; + _client.DownloadFileCompleted += client_DownloadFileCompleted; + + NetworkChange.NetworkAvailabilityChanged += NetworkAvailabilityChanged; + + var credential = TokenManager.GetDownloadCredential(XenAdminConfigManager.Provider); + _client.Headers.Add("Authorization", $"Basic {credential}"); + + int errorCount = 0; + bool needToRetry = false; + + try + { + do + { + if (needToRetry) + Thread.Sleep(SLEEP_TIME_BEFORE_RETRY_MS); + + needToRetry = false; + + _client.Proxy = XenAdminConfigManager.Provider.GetProxyFromSettings(null, false); + + _updateDownloadState = DownloadState.InProgress; + _client.DownloadFileAsync(_updateUri, outputFileName); + + bool updateDownloadCancelling = false; + + while (_updateDownloadState == DownloadState.InProgress) + { + if (!updateDownloadCancelling && (Cancelling || Cancelled)) + { + Description = Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOAD_CANCELLED_DESC; + _client.CancelAsync(); + updateDownloadCancelling = true; + } + + Thread.Sleep(SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS); + } + + if (_updateDownloadState == DownloadState.Cancelled) + throw new CancelledException(); + + if (_updateDownloadState == DownloadState.Error) + { + needToRetry = true; + errorCount++; + + log.ErrorFormat("Failed to download '{0}' (attempt {1} of maximum {2}.", + _updateUri, errorCount, MAX_NUMBER_OF_TRIES); + + if (_updateDownloadError == null) + log.Error("An unknown error occurred."); + else + log.Error(_updateDownloadError); + } + } while (errorCount < MAX_NUMBER_OF_TRIES && needToRetry); + } + finally + { + LogDescriptionChanges = true; + + _client.DownloadProgressChanged -= client_DownloadProgressChanged; + _client.DownloadFileCompleted -= client_DownloadFileCompleted; + + NetworkChange.NetworkAvailabilityChanged -= NetworkAvailabilityChanged; + + _client.Dispose(); + } + + //if this is still the case after having retried MAX_NUMBER_OF_TRIES number of times. + if (_updateDownloadState == DownloadState.Error) + { + log.Error($"Giving up; maximum number of retries ({MAX_NUMBER_OF_TRIES}) has been reached."); + + throw _updateDownloadError ?? new Exception(Messages.ERROR_UNKNOWN); + } + } + + private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + int pc = (int)(95.0 * e.BytesReceived / e.TotalBytesToReceive); + var descr = string.Format(Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DETAILS_DESC, UpdateName, + Util.DiskSizeString(e.BytesReceived, "F1"), + Util.DiskSizeString(e.TotalBytesToReceive)); + ByteProgressDescription = descr; + Tick(pc, descr); + } + + private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) + { + if (e.Cancelled && _updateDownloadState == DownloadState.Error) + { + log.Debug($"XenServer update '{UpdateName}' download cancelled due to network connectivity issues"); + return; + } + + if (e.Cancelled) + { + _updateDownloadState = DownloadState.Cancelled; + log.Debug($"XenServer update '{UpdateName}' download cancelled by the user"); + return; + } + + if (e.Error != null) //failure + { + if (e.Error is WebException wex && wex.Response is HttpWebResponse response) + { + switch (response.StatusCode) + { + case HttpStatusCode.Unauthorized: + log.Error($"Could not download {UpdateName} (401 Unauthorized). Client ID may be invalid or revoked."); + _updateDownloadError = new Exception(Messages.FILESERVICE_ERROR_401); + break; + + case HttpStatusCode.Forbidden: + log.Error($"Could not download {UpdateName} (403 Forbidden). The account has insufficient permissions."); + _updateDownloadError = new Exception(Messages.FILESERVICE_ERROR_403); + break; + + case HttpStatusCode.NotFound: + log.Error($"Could not download {UpdateName} (404 File not found)."); + _updateDownloadError = new Exception(Messages.FILESERVICE_ERROR_404); + break; + + default: + _updateDownloadError = e.Error; + break; + } + } + else + { + _updateDownloadError = e.Error; + } + + log.Debug($"XenServer patch '{UpdateName}' download failed"); + _updateDownloadState = DownloadState.Error; + return; + } + + _updateDownloadState = DownloadState.Completed; + log.Debug($"XenServer update '{UpdateName}' download completed successfully"); + } + + private void NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) + { + if (!e.IsAvailable && _client != null && _updateDownloadState == DownloadState.InProgress) + { + _updateDownloadError = new WebException(Messages.NETWORK_CONNECTIVITY_ERROR); + _updateDownloadState = DownloadState.Error; + _client.CancelAsync(); + } + } + + protected override void UpdateExtractionProgress(int fileIndex, int totalFileCount) + { + PercentComplete = 95 + (int)(5.0 * fileIndex / totalFileCount); + } + + public override void RecomputeCanCancel() + { + CanCancel = !Cancelling && !IsCompleted && _updateDownloadState == DownloadState.InProgress; + } + } + + + public class UnzipUpdateAction : DownloadUnzipUpdateAction + { + private readonly string _zippedUpdatePath; + + public UnzipUpdateAction(string zippedUpdatePath, params string[] updateFileExtensions) + : base(Path.GetFileNameWithoutExtension(zippedUpdatePath), updateFileExtensions) + { + Title = string.Format(Messages.UPDATES_WIZARD_EXTRACT_ACTION_TITLE, zippedUpdatePath); + _zippedUpdatePath = zippedUpdatePath; + } + + protected override void Run() + { + UpdatePath = ExtractFile(_zippedUpdatePath, false); + Description = Messages.COMPLETED; + } + + protected override void UpdateExtractionProgress(int fileIndex, int totalFileCount) + { + PercentComplete = (int)(100.0 * fileIndex / totalFileCount); + } + + public override void RecomputeCanCancel() + { + CanCancel = !Cancelling && !IsCompleted; + } + } +} diff --git a/XenModel/XenModel.csproj b/XenModel/XenModel.csproj index 9ce346a70..81f6c5759 100755 --- a/XenModel/XenModel.csproj +++ b/XenModel/XenModel.csproj @@ -128,7 +128,7 @@ - +