// // Control Event Handlers: // #region private void _extractItemStripMenuItem_Click(object sender, EventArgs e) /// <summary> /// Extracts archive item to file. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void _extractItemStripMenuItem_Click(object sender, EventArgs e) { if (_archiveItemInfoListView.SelectedItems.Count == 1) { var child = _archiveItemInfoListView.SelectedItems[0].Tag as ChildDocument; if (child != null) { try { var saveDialog = new SaveFileDialog(); saveDialog.Title = "Extract Selected Item..."; saveDialog.FileName = child.Name; if (saveDialog.ShowDialog() == DialogResult.OK) { using (var outStream = new FileStream(saveDialog.FileName, FileMode.CreateNew, FileAccess.ReadWrite)) { var result = _archiveExtractor.ExtractItem((int)child.Index, outStream); // Archive item is password protected: if (result == ContentResult.WrongPassword) { string password = null; while (_hostUI.RequestPassword(out password) == DialogResult.OK) { result = _archiveExtractor.ExtractItem((int)child.Index, outStream, password); if (result != ContentResult.WrongPassword) { break; } } if (result != ContentResult.Ok) { _hostUI.ShowMessageBox("Error extracting item, result = " + result.ToString(), "Error"); } } else if (result != ContentResult.Ok) { var msg = "Error extraction item, Error = " + result.ToString(); _hostUI.ShowMessageBox(msg, "Error"); _hostUI.LogMessage(msg); } } } } catch (Exception ex) { _hostUI.ShowMessageBox("Error saving archive item: " + ex.Message, "Archive Extract Error"); } } } }
/// <summary> /// Extracts all archive items to a root output directory using archive folder structure (if any). /// </summary> /// <remarks> /// **WARNING**: Outputting items to the archive folder structure does not check for illegal item name or path characters or for long file paths /// (i.e., paths greater than MAX_PATH), both which could cause an exception. It is left to user to write production level /// code to check for and replace illegal file system characters in mail store folder names and to also ensure that their /// application can handle paths greater than MAX_PATH. /// </remarks> /// <param name="rootOutputPath">Root folder path to extract archive items.</param> /// <param name="passwords">Optional list of passwords to cycle through when encountering an encrypted archive item. Archives can have archive level passwords and/or /// different passwords for different items.</param> public void ExtractItemsToDirectory(string rootOutputPath) { var stopwatch = Stopwatch.StartNew(); TotalItemsExtracted = 0; _rootOutputFolder = rootOutputPath; if (!Directory.Exists(_rootOutputFolder)) { Directory.CreateDirectory(_rootOutputFolder); } var outputDirFiles = Directory.GetFiles(_rootOutputFolder); if (outputDirFiles != null && outputDirFiles.Length > 0) { // This is just an example, so will play it safe: _hostUI.LogMessage("Extraction aborted - there are existing files in output folder, select/create a folder with no existing files."); _hostUI.ShowMessageBox("Extraction aborted - there are existing files in output folder, select/create a folder with no existing files.", "Error"); return; } // // Create archive directory structure first (if there is one) under input argument 'rootOutputPath': // if (_archiveContent.Root != null && _archiveContent.Root.SubFolders.Count > 0) { FileSystemHelper.CreateContainerFolderDirectoryHierarchy(_rootOutputFolder, _archiveContent.Root.SubFolders); } if (_archiveExtractor.IsSolid) { //================================================================================================================ // Solid archive: The archive has 1 or more solid compressed blocks: // // Solid compressed archives are archives (e.g., 7z or Rar) where items are compressed together is pre-defined block sizes // in order to improve compression ratios. It is very very inefficient to randomly extract one item at a time from a solid compressed // archive block, especially if there are 100's to 1000's of items in that block. The way to extract from solid block archives // shown here with callback delegates is the most efficient way: //================================================================================================================ var blockItemResult = _archiveExtractor.ExtractSolidBlockItems(ItemGetStreamCallback, ItemFinishedCallback, _archiveContent.Password); if (blockItemResult == ContentResult.WrongPassword) { var foundPassword = false; string password; // Keep prompting user for passwords until result is not ContentResult.WrongPassword or until user presses "Cancel" button while (_hostUI.RequestPassword(out password) == DialogResult.OK) { blockItemResult = _archiveExtractor.ExtractSolidBlockItems(ItemGetStreamCallback, ItemFinishedCallback, password); if (blockItemResult != ContentResult.WrongPassword) { foundPassword = true; break; } } if (!foundPassword) { _hostUI.LogMessage("Could not extract items from 'solid' archive - archive solid block is encrypted and no valid password was found."); _hostUI.ShowMessageBox("Could not extract items from 'solid' archive - archive solid block is encrypted and no valid password was found.", "Error"); } } } else { //================================================================================================================ // Non-solid archives: //================================================================================================================ var password = _archiveContent.Password; // Use archive level password (if there is one) for 1st try at item level password foreach (var childDoc in _archiveContent.ChildDocuments) { try { if (string.IsNullOrWhiteSpace(childDoc.Name)) { childDoc.Name = string.Format("item_{0}", childDoc.Index); } var filePath = Path.Combine(_rootOutputFolder, childDoc.ContainerRelativePath, childDoc.Name); // // Check for duplicate file paths and if found rename filename with like Windows does for duplicate files, e.g., "filename (2).ext" // Some archive types can have duplicate named files in same directory, so this is a check for that: // FileSystemHelper.CheckForAndCorrectDuplicateItemFilePaths(_itemFilenameByCountDict, childDoc, ref filePath); Stream stream = null; try { stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.ReadWrite); var itemExtractResult = _archiveExtractor.ExtractItem((int)childDoc.Index, stream, password); if (itemExtractResult == ContentResult.Ok) { // Success ++TotalItemsExtracted; continue; } else if (itemExtractResult == ContentResult.WrongPassword) { var foundPassword = false; // Keep prompting user for passwords until result is not ContentResult.WrongPassword or until user presses "Cancel" button while (_hostUI.RequestPassword(out password) == DialogResult.OK) { itemExtractResult = _archiveExtractor.ExtractItem((int)childDoc.Index, stream, password); if (itemExtractResult != ContentResult.WrongPassword) { foundPassword = true; break; } } if (foundPassword) { ++TotalItemsExtracted; continue; } else { // Set item file format identification: childDoc.FormatId = DocumentIdentifier.ContainerUnextractableResult; _hostUI.LogMessage(string.Format("Could not extract item #{0} from archive - archive item is encrypted and no valid password was found.", childDoc.Index)); } } else { // Some level of error has occured: if (itemExtractResult == ContentResult.DataError && stream != null && (stream.Length >= (long)(0.5 * childDoc.Size) && stream.Length <= (long)(1.1 * childDoc.Size))) { // We have a data error but also have at least the expanded size of data extracted from archive - attempt to process this data if // expanded stream is not too much larger than item's 'Size' property. ++TotalItemsExtracted; continue; } else { childDoc.FormatId = DocumentIdentifier.ContainerUnextractableResult; var msg = string.Format("Could not extract item #{0} from archive - item error = '{1}'", childDoc.Index, itemExtractResult.ToString()); _hostUI.LogMessage(msg); } } } finally { if (stream != null) { stream.Dispose(); } } } catch { childDoc.FormatId = DocumentIdentifier.ContainerUnextractableResult; } } } stopwatch.Stop(); TotalElapsedTimeMs = stopwatch.Elapsed.TotalMilliseconds; }