2017-01-16 20:59:50 +01:00
/ * Copyright ( c ) Citrix Systems , Inc .
2013-06-24 13:41:48 +02:00
* All rights reserved .
*
* 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 ;
2018-03-22 18:47:16 +01:00
using System.Linq ;
2018-07-18 15:23:37 +02:00
using System.Net.NetworkInformation ;
2013-06-24 13:41:48 +02:00
using XenCenterLib.Archive ;
namespace XenAdmin.Actions
{
2018-03-22 18:47:16 +01:00
internal enum DownloadState
{
InProgress ,
Cancelled ,
Completed ,
Error
} ;
2013-06-24 13:41:48 +02:00
2018-06-27 19:05:00 +02:00
public class DownloadAndUnzipXenServerPatchAction : AsyncAction , IByteProgressAction
2013-06-24 13:41:48 +02:00
{
2018-03-22 18:47:16 +01:00
private static readonly log4net . ILog log =
log4net . LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2013-06-24 13:41:48 +02:00
2016-12-01 18:48:37 +01:00
private const int SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS = 900 ;
2018-07-19 16:47:48 +02:00
private const int SLEEP_TIME_BEFORE_RETRY_MS = 5000 ;
2018-03-22 18:47:16 +01:00
2018-07-19 16:47:48 +02:00
//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.
2018-03-22 18:47:16 +01:00
private const int MAX_NUMBER_OF_TRIES = 5 ;
2013-06-24 13:41:48 +02:00
private readonly Uri address ;
2018-03-22 18:47:16 +01:00
private readonly string outputFileName ;
2013-06-24 13:41:48 +02:00
private readonly string updateName ;
2018-03-22 18:47:16 +01:00
private readonly string [ ] updateFileSuffixes ;
2017-07-21 16:56:27 +02:00
private readonly bool downloadUpdate ;
2018-11-07 16:21:10 +01:00
private readonly bool skipUnzipping ;
2013-06-24 13:41:48 +02:00
private DownloadState patchDownloadState ;
private Exception patchDownloadError ;
2018-03-22 18:47:16 +01:00
public string PatchPath { get ; private set ; }
2013-06-24 13:41:48 +02:00
2018-06-27 19:05:00 +02:00
public string ByteProgressDescription { get ; set ; }
2018-03-22 18:47:16 +01:00
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 )
2013-06-24 13:41:48 +02:00
{
updateName = patchName ;
address = uri ;
2017-07-21 16:56:27 +02:00
downloadUpdate = address ! = null ;
2018-03-22 18:47:16 +01:00
updateFileSuffixes = ( from item in updateFileExtensions select '.' + item ) . ToArray ( ) ;
2018-11-07 16:21:10 +01:00
skipUnzipping = downloadUpdate & & updateFileSuffixes . Any ( item = > address . ToString ( ) . Contains ( item ) ) ;
2018-03-22 18:47:16 +01:00
this . outputFileName = outputFileName ;
2013-06-24 13:41:48 +02:00
}
2018-07-18 15:23:37 +02:00
private WebClient client ;
2013-06-24 13:41:48 +02:00
private void DownloadFile ( )
{
2016-12-01 18:48:37 +01:00
int errorCount = 0 ;
2018-07-19 16:47:48 +02:00
bool needToRetry = false ;
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
client = new WebClient ( ) ;
//register download events
client . DownloadProgressChanged + = client_DownloadProgressChanged ;
client . DownloadFileCompleted + = client_DownloadFileCompleted ;
2016-12-01 18:48:37 +01:00
2018-07-19 16:47:48 +02:00
// register event handler to detect changes in network connectivity
2018-07-18 15:23:37 +02:00
NetworkChange . NetworkAvailabilityChanged + = NetworkAvailabilityChanged ;
try
{
2018-07-19 16:47:48 +02:00
do
2013-06-24 13:41:48 +02:00
{
2018-07-19 16:47:48 +02:00
if ( needToRetry )
Thread . Sleep ( SLEEP_TIME_BEFORE_RETRY_MS ) ;
2018-07-18 15:23:37 +02:00
needToRetry = false ;
client . Proxy = XenAdminConfigManager . Provider . GetProxyFromSettings ( null , false ) ;
2017-02-02 19:29:19 +01:00
2018-07-18 15:23:37 +02:00
//start the download
patchDownloadState = DownloadState . InProgress ;
client . DownloadFileAsync ( address , outputFileName ) ;
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
bool patchDownloadCancelling = false ;
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
//wait for the file to be downloaded
while ( patchDownloadState = = DownloadState . InProgress )
{
if ( ! patchDownloadCancelling & & ( Cancelling | | Cancelled ) )
2016-12-01 18:48:37 +01:00
{
2018-07-18 15:23:37 +02:00
Description = Messages . DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOAD_CANCELLED_DESC ;
client . CancelAsync ( ) ;
patchDownloadCancelling = true ;
2016-12-01 18:48:37 +01:00
}
2018-07-18 15:23:37 +02:00
Thread . Sleep ( SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS ) ;
}
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
if ( patchDownloadState = = DownloadState . Cancelled )
throw new CancelledException ( ) ;
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
if ( patchDownloadState = = DownloadState . Error )
{
needToRetry = true ;
2016-12-01 18:48:37 +01:00
2018-07-18 15:23:37 +02:00
// this many errors so far - including this one
errorCount + + ;
2018-03-22 18:47:16 +01:00
2018-07-18 15:23:37 +02:00
// 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 ) ;
log . Error ( patchDownloadError ? ? new Exception ( Messages . ERROR_UNKNOWN ) ) ;
2016-12-01 18:48:37 +01:00
}
2018-07-19 16:47:48 +02:00
} while ( errorCount < MAX_NUMBER_OF_TRIES & & needToRetry ) ;
2013-06-24 13:41:48 +02:00
}
2018-07-18 15:23:37 +02:00
finally
{
//deregister download events
client . DownloadProgressChanged - = client_DownloadProgressChanged ;
client . DownloadFileCompleted - = client_DownloadFileCompleted ;
NetworkChange . NetworkAvailabilityChanged - = NetworkAvailabilityChanged ;
client . Dispose ( ) ;
}
2013-06-24 13:41:48 +02:00
2018-07-19 16:47:48 +02:00
//if this is still the case after having retried MAX_NUMBER_OF_TRIES number of times.
2013-06-24 13:41:48 +02:00
if ( patchDownloadState = = DownloadState . Error )
2016-12-01 18:48:37 +01:00
{
2018-07-19 16:47:48 +02:00
log . ErrorFormat ( "Giving up - Maximum number of retries ({0}) has been reached." , MAX_NUMBER_OF_TRIES ) ;
2018-03-22 18:47:16 +01:00
2013-06-24 13:41:48 +02:00
MarkCompleted ( patchDownloadError ? ? new Exception ( Messages . ERROR_UNKNOWN ) ) ;
2016-12-01 18:48:37 +01:00
}
2013-06-24 13:41:48 +02:00
}
2018-07-18 15:23:37 +02:00
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 ( ) ;
}
}
2013-06-24 13:41:48 +02:00
private void ExtractFile ( )
{
ArchiveIterator iterator = null ;
try
{
2018-03-22 18:47:16 +01:00
using ( Stream stream = new FileStream ( outputFileName , FileMode . Open , FileAccess . Read ) )
2013-06-24 13:41:48 +02:00
{
iterator = ArchiveFactory . Reader ( ArchiveFactory . Type . Zip , stream ) ;
DotNetZipZipIterator zipIterator = iterator as DotNetZipZipIterator ;
if ( zipIterator ! = null )
{
zipIterator . CurrentFileExtractProgressChanged + =
archiveIterator_CurrentFileExtractProgressChanged ;
}
while ( iterator . HasNext ( ) )
{
2018-03-22 18:47:16 +01:00
string currentExtension = Path . GetExtension ( iterator . CurrentFileName ( ) ) ;
2016-10-19 12:29:57 +02:00
2018-03-22 18:47:16 +01:00
if ( updateFileSuffixes . Any ( item = > item = = currentExtension ) )
2013-06-24 13:41:48 +02:00
{
2018-03-22 18:47:16 +01:00
string path = downloadUpdate
? Path . Combine ( Path . GetDirectoryName ( outputFileName ) , iterator . CurrentFileName ( ) )
2017-07-21 16:56:27 +02:00
: Path . Combine ( Path . GetTempPath ( ) , iterator . CurrentFileName ( ) ) ;
2013-06-24 13:41:48 +02:00
2018-03-22 18:47:16 +01:00
log . InfoFormat (
"Found '{0}' in the downloaded archive when looking for a '{1}' file. Extracting..." ,
iterator . CurrentFileName ( ) , currentExtension ) ;
2016-10-19 12:29:57 +02:00
2013-06-24 13:41:48 +02:00
using ( Stream outputStream = new FileStream ( path , FileMode . Create ) )
{
iterator . ExtractCurrentFile ( outputStream ) ;
PatchPath = path ;
2016-10-19 12:29:57 +02:00
2018-01-19 14:34:20 +01:00
log . InfoFormat ( "Update file extracted to '{0}'" , path ) ;
2018-03-22 18:47:16 +01:00
2013-06-24 13:41:48 +02:00
break ;
}
}
}
if ( zipIterator ! = null )
{
2018-11-07 16:21:10 +01:00
zipIterator . CurrentFileExtractProgressChanged - = archiveIterator_CurrentFileExtractProgressChanged ;
2013-06-24 13:41:48 +02:00
}
}
}
catch ( Exception e )
{
log . ErrorFormat ( "Exception occurred when extracting downloaded archive: {0}" , e . Message ) ;
throw new Exception ( Messages . DOWNLOAD_AND_EXTRACT_ACTION_EXTRACTING_ERROR ) ;
}
finally
{
if ( iterator ! = null )
iterator . Dispose ( ) ;
2017-07-21 16:56:27 +02:00
if ( downloadUpdate )
2018-03-22 18:47:16 +01:00
{
try { File . Delete ( outputFileName ) ; }
catch { }
}
2013-06-24 13:41:48 +02:00
}
2018-03-22 18:47:16 +01:00
2017-07-21 16:56:27 +02:00
if ( string . IsNullOrEmpty ( PatchPath ) & & downloadUpdate )
2013-06-24 13:41:48 +02:00
{
MarkCompleted ( new Exception ( Messages . DOWNLOAD_AND_EXTRACT_ACTION_FILE_NOT_FOUND ) ) ;
2018-03-22 18:47:16 +01:00
log . InfoFormat (
"The downloaded archive does not contain a file with any of the following extensions: {0}" ,
string . Join ( ", " , updateFileSuffixes ) ) ;
2013-06-24 13:41:48 +02:00
}
}
protected override void Run ( )
{
2017-07-21 16:56:27 +02:00
if ( downloadUpdate )
{
2018-03-22 18:47:16 +01:00
log . InfoFormat ( "Downloading update '{0}' (from '{1}') to '{2}'" , updateName , address , outputFileName ) ;
2017-07-21 16:56:27 +02:00
Description = string . Format ( Messages . DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DESC , updateName ) ;
LogDescriptionChanges = false ;
DownloadFile ( ) ;
LogDescriptionChanges = true ;
2013-06-24 13:41:48 +02:00
2017-07-21 16:56:27 +02:00
if ( IsCompleted | | Cancelled )
return ;
2013-06-24 13:41:48 +02:00
2017-07-21 16:56:27 +02:00
if ( Cancelling )
throw new CancelledException ( ) ;
}
2013-06-24 13:41:48 +02:00
2018-11-07 16:21:10 +01:00
if ( skipUnzipping )
2018-03-22 18:47:16 +01:00
{
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 . ErrorFormat ( "Exception occurred when preparing archive: {0}" , e . Message ) ;
2018-06-27 14:47:32 +02:00
throw ;
2018-03-22 18:47:16 +01:00
}
}
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 ) ;
}
2013-06-24 13:41:48 +02:00
Description = Messages . COMPLETED ;
MarkCompleted ( ) ;
}
void archiveIterator_CurrentFileExtractProgressChanged ( object sender , ExtractProgressChangedEventArgs e )
{
2017-07-21 16:56:27 +02:00
int pc = downloadUpdate ? 95 + ( int ) ( 5.0 * e . BytesTransferred / e . TotalBytesToTransfer ) : ( int ) ( 100.0 * e . BytesTransferred / e . TotalBytesToTransfer ) ;
2018-06-27 14:47:32 +02:00
PercentComplete = pc ;
2013-06-24 13:41:48 +02:00
}
void client_DownloadProgressChanged ( object sender , DownloadProgressChangedEventArgs e )
{
2015-03-19 15:22:49 +01:00
int pc = ( int ) ( 95.0 * e . BytesReceived / e . TotalBytesToReceive ) ;
2018-06-27 14:47:32 +02:00
var descr = string . Format ( Messages . DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOADING_DETAILS_DESC , updateName ,
2018-06-28 17:28:57 +02:00
Util . DiskSizeString ( e . BytesReceived , "F1" ) ,
2015-03-19 15:22:49 +01:00
Util . DiskSizeString ( e . TotalBytesToReceive ) ) ;
2018-06-27 19:05:00 +02:00
ByteProgressDescription = descr ;
2018-06-27 14:47:32 +02:00
Tick ( pc , descr ) ;
2013-06-24 13:41:48 +02:00
}
void client_DownloadFileCompleted ( object sender , AsyncCompletedEventArgs e )
{
2018-07-18 15:23:37 +02:00
if ( e . Cancelled & & patchDownloadState = = DownloadState . Error ) // cancelled due to network connectivity issue (see NetworkAvailabilityChanged)
return ;
2013-06-24 13:41:48 +02:00
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 ( )
{
}
2017-10-02 17:34:23 +02:00
2013-06-24 13:41:48 +02:00
}
}