Skip to content

Commit

Permalink
Issue #116 in progress in GUI.
Browse files Browse the repository at this point in the history
  • Loading branch information
activescott committed Nov 26, 2018
1 parent 2622a33 commit b2d07d9
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 54 deletions.
162 changes: 111 additions & 51 deletions src/LessMsi.Core/Msi/Wixtracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ namespace LessMsi.Msi
{
public class Wixtracts
{
#region class ExtractionProgress

#region class ExtractionProgress Stuff
/// <summary>
/// Provides progress information during an extraction operatation.
/// </summary>
Expand All @@ -50,15 +49,22 @@ public class ExtractionProgress : IAsyncResult
private string _currentFileName;
private ExtractionActivity _activity;
private readonly ManualResetEvent _waitSignal;
private readonly AsyncCallback _callback;
private readonly AsyncCallback _progressCallback;
private readonly ExtractionErrorHandler _errorCallback;
private readonly int _totalFileCount;
private int _filesExtracted;

public ExtractionProgress(AsyncCallback progressCallback, int totalFileCount)
: this(progressCallback, totalFileCount, null)
{
}

public ExtractionProgress(AsyncCallback progressCallback, int totalFileCount, ExtractionErrorHandler errorCallback)
{
_activity = ExtractionActivity.Initializing;
_currentFileName = "";
_callback = progressCallback;
_progressCallback = progressCallback;
_errorCallback = errorCallback;
_waitSignal = new ManualResetEvent(false);
_totalFileCount = totalFileCount;
_filesExtracted = 0;
Expand All @@ -75,11 +81,23 @@ internal void ReportProgress(ExtractionActivity activity, string currentFileName
if (this.IsCompleted)
_waitSignal.Set();

if (_callback != null)
_callback(this);
if (_progressCallback != null)
_progressCallback(this);
}
}

/// <summary>
/// Called internally to report errors to the calling application.
/// </summary>
/// <seealso cref="ExtractionErrorHandler"/>
internal ExtractionErrorResponse ReportError(string error, string fileName, string cabinetName)
{
if (_errorCallback != null)
return _errorCallback(this, error, fileName, cabinetName);
else
return ExtractionErrorResponse.Abort;
}

/// <summary>
/// The total number of files to be extracted for this operation.
/// </summary>
Expand Down Expand Up @@ -186,8 +204,6 @@ public bool IsCompleted
#endregion
}

#endregion

#region enum ExtractionActivity

/// <summary>
Expand All @@ -203,6 +219,33 @@ public enum ExtractionActivity

#endregion

#region ExtractionErrorResponse
/// <summary>
/// Allows
/// </summary>
public enum ExtractionErrorResponse
{
/// <summary>
/// Indicates extraction should attempt to continue.
/// </summary>
Continue,
/// <summary>
/// Indicates extraction should be aborted.
/// </summary>
Abort
}
#endregion

/// <summary>
/// Defines the signature for a method to be notified of errors.
/// </summary>
/// <param name="error">The error message.</param>
/// <param name="fileName">The name of the file that an error occurred while extracting.</param>
/// <param name="cabinetName">The name of the cabinet containing the file that the extraction error occurred with.</param>
/// <returns>Indicates how to handle the error.</returns>
public delegate ExtractionErrorResponse ExtractionErrorHandler(ExtractionProgress progressState, string error, string fileName, string cabinetName);
#endregion

public static void ExtractFiles(Path msi, string outputDir)
{
ExtractFiles(msi, outputDir, new string[0], null);
Expand All @@ -220,49 +263,19 @@ public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesTo
ExtractFiles(msi, outputDir, msiFiles, progressCallback);
}

private static MsiFile[] GetMsiFileFromFileNames(Path msi, string[] fileNamesToExtract)
{
var msiFiles = MsiFile.CreateMsiFilesFromMSI(msi);
Array.Sort(msiFiles, (f1, f2) => string.Compare(f1.LongFileName, f2.LongFileName, StringComparison.InvariantCulture));

var fileNamesToExtractAsMsiFiles = new List<MsiFile>();
foreach (var fileName in fileNamesToExtract)
{
var found = Array.BinarySearch(msiFiles, fileName, FileNameComparer.Default);
if (found >= 0)
fileNamesToExtractAsMsiFiles.Add(msiFiles[found]);
else
Console.WriteLine("File {0} was not found in the msi.", fileName);
}
return fileNamesToExtractAsMsiFiles.ToArray();
}

private sealed class FileNameComparer : IComparer
{
static readonly FileNameComparer _default = new FileNameComparer();

public static FileNameComparer Default
{
get { return _default; }
}

int IComparer.Compare(object x, object y)
{
//expect two MsiFile or one MsiFile and one string:
var getName = new Func<object, string>((object fileOrName) => fileOrName is MsiFile ? ((MsiFile) fileOrName).LongFileName : (string)fileOrName);
var xName = getName(x);
var yName = getName(y);
return string.Compare(xName, yName, StringComparison.InvariantCulture);
}
}
public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExtract, AsyncCallback progressCallback)
{
ExtractFiles(msi, outputDir, filesToExtract, progressCallback, null);
}

/// <summary>
/// <summary>
/// Extracts the compressed files from the specified MSI file to the specified output directory.
/// If specified, the list of <paramref name="filesToExtract"/> objects are the only files extracted.
/// </summary>
/// <param name="filesToExtract">The files to extract or null or empty to extract all files.</param>
/// <param name="progressCallback">Will be called during during the operation with progress information, and upon completion. The argument will be of type <see cref="ExtractionProgress"/>.</param>
public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExtract, AsyncCallback progressCallback)
/// <param name="errorCallback">Will be called during the operation with any error information.</param>
public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExtract, AsyncCallback progressCallback, ExtractionErrorHandler errorCallback)
{
if (msi.IsEmpty)
throw new ArgumentNullException("msi");
Expand All @@ -278,7 +291,7 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
if (filesToExtract == null || filesToExtract.Length < 1)
filesToExtract = MsiFile.CreateMsiFilesFromMSI(msidb);

progress = new ExtractionProgress(progressCallback, filesToExtract.Length);
progress = new ExtractionProgress(progressCallback, filesToExtract.Length, errorCallback);

if (!FileSystem.Exists(msi))
{
Expand Down Expand Up @@ -339,7 +352,17 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
// ReSharper restore HeuristicUnreachableCode
}
Trace.WriteLine(string.Concat("Extracting File \'", compressedFile.Filename, "\' to \'", destName, "\'"));
compressedFile.ExtractTo(destName.PathString);
try
{
compressedFile.ExtractTo(destName.PathString);
}
catch (Exception e)
{
var errorMessage = string.Format("Error extracting file \"{0}\" from cab \"{1}\". Error detail: {2}", entry.LongFileName, decompressor.LocalFilePath, e.Message);
var result = progress.ReportError(errorMessage, entry.LongFileName, decompressor.LocalFilePath);
if (result == ExtractionErrorResponse.Abort)
throw new Exception(errorMessage);
}
filesExtractedSoFar++;
}
}
Expand All @@ -362,10 +385,47 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
}
}

/// <summary>
/// Deletes a file even if it is readonly.
/// </summary>
private static void DeleteFileForcefully(Path localFilePath)
private static MsiFile[] GetMsiFileFromFileNames(Path msi, string[] fileNamesToExtract)
{
var msiFiles = MsiFile.CreateMsiFilesFromMSI(msi);
Array.Sort(msiFiles, (f1, f2) => string.Compare(f1.LongFileName, f2.LongFileName, StringComparison.InvariantCulture));

var fileNamesToExtractAsMsiFiles = new List<MsiFile>();
foreach (var fileName in fileNamesToExtract)
{
var found = Array.BinarySearch(msiFiles, fileName, FileNameComparer.Default);
if (found >= 0)
fileNamesToExtractAsMsiFiles.Add(msiFiles[found]);
else
Console.WriteLine("File {0} was not found in the msi.", fileName);
}
return fileNamesToExtractAsMsiFiles.ToArray();
}

private sealed class FileNameComparer : IComparer
{
static readonly FileNameComparer _default = new FileNameComparer();

public static FileNameComparer Default
{
get { return _default; }
}

int IComparer.Compare(object x, object y)
{
//expect two MsiFile or one MsiFile and one string:
var getName = new Func<object, string>((object fileOrName) => fileOrName is MsiFile ? ((MsiFile)fileOrName).LongFileName : (string)fileOrName);
var xName = getName(x);
var yName = getName(y);
return string.Compare(xName, yName, StringComparison.InvariantCulture);
}
}


/// <summary>
/// Deletes a file even if it is readonly.
/// </summary>
private static void DeleteFileForcefully(Path localFilePath)
{
// In github issue #4 found that the cab files in the Win7SDK have the readonly attribute set and File.Delete fails to delete them. Explicitly unsetting that bit before deleting works okay...
FileSystem.RemoveFile(localFilePath, true);
Expand Down
37 changes: 37 additions & 0 deletions src/LessMsi.Gui/ExtractionProgressDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
using System.Drawing;
using System.Windows.Forms;
using LessMsi.Msi;
using System.Collections.Generic;

namespace LessMsi.Gui
{
internal class ExtractionProgressDialog : Form
{
private ProgressBar _progressBar;
private Label _label;
private readonly List<string> _errorFiles = new List<string>();
private bool _continuePromptingErrors = true;

public ExtractionProgressDialog(Form owner)
{
Expand Down Expand Up @@ -94,5 +97,39 @@ public void UpdateProgress(Wixtracts.ExtractionProgress progress)
this.Invalidate(true);
this.Update();
}

internal void ShowAnyFinalMessages()
{
if (this._errorFiles.Count > 0)
{
const int MAX_FILES = 10;
var displayList = this._errorFiles.GetRange(0, Math.Min(MAX_FILES, this._errorFiles.Count));
var displayString = string.Join(", ", displayList);
var displayDelta = _errorFiles.Count - displayList.Count;
string postfix = displayDelta > 0 ? string.Format("...and {0} more", displayDelta) : "";
MessageBox.Show(this,
string.Format("The following files failed to extract: {0}{1}.", displayString, postfix)
);
}
}

internal Wixtracts.ExtractionErrorResponse ExtractionErrorHandler(Wixtracts.ExtractionProgress progressState, string error, string fileName, string cabinetName)
{
this._errorFiles.Add(fileName);
if (this._continuePromptingErrors)
{
var result = MessageBox.Show(this,
string.Format("The following error occurred extracting a file:\r\n{0}\r\n Choose 'Abort' to abort extraction. Choose 'Retry' to attempt to continue extracting files, but continue reporting future errors. Choose 'Ignore' to ignore all future errors and continue extraction.", fileName),
"Extraction Error",
MessageBoxButtons.AbortRetryIgnore
);
this._continuePromptingErrors = (result != DialogResult.Ignore);
return result == DialogResult.Abort ? Wixtracts.ExtractionErrorResponse.Abort : Wixtracts.ExtractionErrorResponse.Continue;
}
else
{
return Wixtracts.ExtractionErrorResponse.Continue;
}
}
}
}
11 changes: 8 additions & 3 deletions src/LessMsi.Gui/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -920,9 +920,14 @@ private void btnExtract_Click(object sender, EventArgs e)


var filesToExtract = selectedFiles.ToArray();
Wixtracts.ExtractFiles(msiFile, outputDir, filesToExtract,
new AsyncCallback(progressDialog.UpdateProgress));
}
Wixtracts.ExtractFiles(msiFile,
outputDir,
filesToExtract,
new AsyncCallback(progressDialog.UpdateProgress),
progressDialog.ExtractionErrorHandler
);
progressDialog.ShowAnyFinalMessages();
}
catch (Exception err)
{
MessageBox.Show(this,
Expand Down

0 comments on commit b2d07d9

Please sign in to comment.