/// <summary> /// Method compares the downloaded data file to the existing data /// file to check if the update is required. This will prevent file /// switching if the data file was downloaded but is not newer than /// the existing data file. /// </summary> /// <remarks> /// The following conditions must be met for the data file to be /// considered newer than the current master data file: /// 1. Current master data file does not exist. /// 2. If the published dates are not the same. /// 3. If the number of properties is not the same. /// </remarks> /// <param name="binaryFile"> /// Current file to compare against. /// </param> /// <param name="decompressedTempFile"> /// Path to the decompressed downloaded file. /// </param> /// <returns>The current state of the update process.</returns> private static AutoUpdateStatus ValidateDownloadedFile( FileInfo binaryFile, FileInfo decompressedTempFile) { AutoUpdateStatus status = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; if (decompressedTempFile.Exists) { // This will throw an exception if the downloaded data file // can't be used to get the required attributes. The exception // is a key part of the validation process. DataSetAttributes tempAttrs = new DataSetAttributes( decompressedTempFile); // If the current binary file exists then compare the two for // the same published date and same properties. If either value // is different then the data file should be accepted. If // they're the same then the update is not needed. if (binaryFile.Exists) { DataSetAttributes binaryAttrs = new DataSetAttributes( binaryFile); if (binaryAttrs.Published != tempAttrs.Published || binaryAttrs.PropertyCount != tempAttrs.PropertyCount) { status = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; } else { status = AutoUpdateStatus.AUTO_UPDATE_NOT_NEEDED; } } } return(status); }
/// <summary> /// Method performs the actual download by setting up and sending /// request and processing the response. /// </summary> /// <param name="binaryFile"> /// File reference for the current data file. /// </param> /// <param name="client"> /// Web client used to download the URL provided. /// </param> /// <param name="compressedTempFile"> /// File to write compressed downloaded. /// </param> /// <param name="fullUrl"> /// URL to use to download the data file. /// </param> /// <returns>The current status of the overall process.</returns> private static AutoUpdateStatus DownloadFile( FileInfo binaryFile, FileInfo compressedTempFile, WebClient client, Uri fullUrl) { AutoUpdateStatus result = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; // Set the last modified header if available from the current // binary data file. if (binaryFile.Exists) { client.Headers.Add( HttpRequestHeader.LastModified, binaryFile.LastWriteTimeUtc.ToString("R")); } // If the response is okay then download the file to the temporary // compressed data file. If not then set the response code // accordingly. try { client.DownloadFile(fullUrl, compressedTempFile.FullName); } catch (SecurityException) { result = AutoUpdateStatus.AUTO_UPDATE_HTTPS_ERR; } catch (WebException ex) { //Server response was not 200. Data download can not commence. switch (((HttpWebResponse)ex.Response).StatusCode) { // Note: needed because TooManyRequests is not available in // earlier versions of the HttpStatusCode enum. case ((HttpStatusCode)429): result = AutoUpdateStatus. AUTO_UPDATE_ERR_429_TOO_MANY_ATTEMPTS; throw; case HttpStatusCode.NotModified: result = AutoUpdateStatus.AUTO_UPDATE_NOT_NEEDED; break; case HttpStatusCode.Forbidden: result = AutoUpdateStatus.AUTO_UPDATE_ERR_403_FORBIDDEN; throw; default: result = AutoUpdateStatus.AUTO_UPDATE_HTTPS_ERR; throw; } } EventLog.Info("Mobile detection data file successfully downloaded to '{0}'", compressedTempFile.FullName); return(result); }
/// <summary> /// Constructor /// </summary> /// <param name="message"> /// The exception message /// </param> /// <param name="innerException"> /// The inner exception that triggered this exception. /// </param> /// <param name="status"> /// The <see cref="AutoUpdateStatus"/> associated with this exception. /// </param> public DataUpdateException( string message, Exception innerException, AutoUpdateStatus status) : base(message, innerException) { Status = status; }
/// <summary> /// Updated internally from the AutoUpdateHostedService to /// update the status. /// </summary> /// <param name="newStatus">The new state.</param> /// <param name="ex">An exception can optionally be provded if the status is AutoUpdateStatus.Error</param> internal void Update(AutoUpdateStatus newStatus, Exception ex = null) { if (ex != null && newStatus != AutoUpdateStatus.Error) { throw new ArgumentException("An exception parameter can only be supplied if the status is AutoUpdateStatus.Error", nameof(ex)); } Status = newStatus; Exception = ex; }
/// <summary> /// Verifies that the data has been downloaded correctly by comparing /// an MD5 hash off the downloaded data with one taken before the data /// was sent, which is stored in a response header. /// </summary> /// <param name="client"> /// The Premium data download connection. /// </param> /// <param name="compressedTempFile"> /// The path to compressed data file that has been downloaded. /// </param> /// <returns>True if the hashes match, otherwise false.</returns> private static AutoUpdateStatus CheckedDownloadedFileMD5( WebClient client, FileInfo compressedTempFile) { AutoUpdateStatus status = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; string serverHash = client.ResponseHeaders["Content-MD5"]; string downloadHash = GetMd5Hash(compressedTempFile); if (serverHash == null || serverHash.Equals(downloadHash) == false) { status = AutoUpdateStatus.AUTO_UPDATE_ERR_MD5_VALIDATION_FAILED; } return(status); }
/// <summary> /// Reads a source GZip file and writes the uncompressed data to /// destination file. /// </summary> /// <param name="destinationPath"> /// Path to GZip file to load from. /// </param> /// <param name="sourcePath"> /// Path to file to write the uncompressed data to. /// </param> /// <returns>The current state of the update process.</returns> private static AutoUpdateStatus Decompress( FileInfo sourcePath, FileInfo destinationPath) { AutoUpdateStatus status = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; using (var fis = new GZipStream( sourcePath.OpenRead(), CompressionMode.Decompress)) { using (var fos = destinationPath.Create()) { fis.CopyTo(fos); } } return(status); }
/// <summary> /// Method represents the final stage of the auto update process. The /// uncompressed file is swapped in place of the existing master file. /// </summary> /// <param name="binaryFile"> /// Path to a binary data that should be set to the downloaded data. /// </param> /// <param name="client"> /// Used to get the Last-Modified HTTP header value. /// </param> /// <param name="uncompressedTempFile"> /// File object containing the uncompressed version of the data file /// downloaded from 51Degrees update server. /// </param> /// <returns>The current state of the update process.</returns> private static AutoUpdateStatus ActivateDownloadedFile( WebClient client, FileInfo binaryFile, FileInfo uncompressedTempFile) { AutoUpdateStatus status = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; bool backedUp = true; FileInfo tempCopyofCurrentMaster = new FileInfo( binaryFile.FullName + ".replacing"); try { // Keep a copy of the old data in case we need to go back to it. binaryFile.Refresh(); if (binaryFile.Exists) { try { File.Move(binaryFile.FullName, tempCopyofCurrentMaster.FullName); backedUp = true; } catch { // The current master file can not be moved so the // backup has not happened. Do not continue to try and // update the data file. backedUp = false; } } // If the backup of the master data file exists then switch the // files. if (backedUp) { try { // Copy the new file to the master file. File.Move(uncompressedTempFile.FullName, binaryFile.FullName); // Set the binary file's last modified date to the one // provided from the web server with the download. This // date will be used when checking for future updates // to avoid downloading the file if there is no update. binaryFile.LastWriteTimeUtc = GetLastModified(client); status = AutoUpdateStatus.AUTO_UPDATE_SUCCESS; } catch { status = AutoUpdateStatus.AUTO_UPDATE_NEW_FILE_CANT_RENAME; } } else { status = AutoUpdateStatus.AUTO_UPDATE_MASTER_FILE_CANT_RENAME; } } catch (Exception ex) { binaryFile.Refresh(); if (binaryFile.Exists == false && tempCopyofCurrentMaster.Exists == true) { tempCopyofCurrentMaster.MoveTo(binaryFile.FullName); } throw ex; } finally { tempCopyofCurrentMaster.Refresh(); if (tempCopyofCurrentMaster.Exists) { tempCopyofCurrentMaster.Delete(); } } return(status); }
/// <summary> /// Downloads and updates the premium data file. /// </summary> /// <param name="licenceKeys"> /// The licence key to use for the update request. /// </param> /// <param name="binaryFile"> /// Location of the master data file. /// </param> /// <returns> /// The result of the download to enable user reporting. /// </returns> private static AutoUpdateStatus Download( IList <string> licenceKeys, FileInfo binaryFile) { AutoUpdateStatus result = AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS; // Set the three files needed to support the download, verification // and eventual activation. var compressedTempFile = GetTempFileName(binaryFile); var uncompressedTempFile = GetTempFileName(binaryFile); try { // Acquire a lock so that only one thread can enter this // critical section at any given time. This is required to // prevent multiple threads from performing the update // simultaneously, i.e. if more than one thread is capable of // invoking AutoUpdate. _autoUpdateSignal.WaitOne(); // Download the device data, decompress, check validity and // finally replace the existing data file if all okay. var client = new WebClient(); result = DownloadFile( binaryFile, compressedTempFile, client, FullUrl(licenceKeys)); if (result == AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS) { result = CheckedDownloadedFileMD5( client, compressedTempFile); } if (result == AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS) { result = Decompress( compressedTempFile, uncompressedTempFile); } if (result == AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS) { result = ValidateDownloadedFile( binaryFile, uncompressedTempFile); } if (result == AutoUpdateStatus.AUTO_UPDATE_IN_PROGRESS) { result = ActivateDownloadedFile( client, binaryFile, uncompressedTempFile); } } finally { try { if (compressedTempFile.Exists) { compressedTempFile.Delete(); } if (uncompressedTempFile.Exists) { uncompressedTempFile.Delete(); } } finally { // No matter what, release the critical section lock. _autoUpdateSignal.Set(); } } return(result); }
/// <summary> /// Updated internally from the AutoUpdateHostedService to /// update the status. /// </summary> internal void Update(AutoUpdateStatus newStatus) { Status = newStatus; }