/// <summary> /// List the contents of the given compressed archive. /// </summary> /// <param name="compressedArchiveAccess">An instance of <see cref="ICompressedArchiveAccess"/> whose contents are to be listed.</param> /// <param name="locationInArchive">A location relative to the root of the archive. The special values <c>null</c>, <c>string.Empty</c>, '\', '/', or '.' indicate the root. Otherwise, must end with a directory separator character.</param> /// <param name="includeContainers">If <c>true</c>, include entries that may contain other entries, such as other compressed archives and directories.</param> /// <param name="recurse">If <c>true</c>, list all contents from <paramref name="locationInArchive"/> and below, recursively. The contents of nested archives will also be listed.</param> /// <returns>The list of entries, which may include entries that could contain more items, depending on the value of <paramref name="includeContainers"/>. /// Entry names are always relative to <paramref name="compressedArchiveAccess"/>. Path separators will be normalized to forward slash.</returns> /// <remarks>NOTE: Large and /or deeply nested archives may incur performance and disk penalties. Use with care!</remarks> /// <exception cref="ArgumentNullException">Thrown if <paramref name="compressedArchiveAccess"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="locationInArchive"/> is malformed i.e. is not null or empty, or does not end with a directory separator character.</exception> /// <exception cref="FileNotFoundException">Thrown if <paramref name="locationInArchive"/> identifies a nested archive that cannot be located.</exception> public static IEnumerable <string> ListContents(this ICompressedArchiveAccess compressedArchiveAccess, string locationInArchive, bool includeContainers, bool recurse) { var entries = compressedArchiveAccess.ListEntries(locationInArchive, includeContainers, recurse); var contents = entries.Select(e => e.Name); return(contents); }
public void ICompressedArchiveAccess_ListWithNullArchive_ThrowsArgumentNullException() { ICompressedArchiveAccess archive = null; Assert.Throws <ArgumentNullException>(() => archive.ListEntries(null, includeContainers: false)); Assert.Throws <ArgumentNullException>(() => archive.ListContents(null, includeContainers: false)); }
/// <summary> /// Creates an instance of <see cref="ICompressedArchiveAccess"/> that can be used to access a nested archive. /// </summary> /// <param name="parentArchiveAccess">The parent archive, which contains the nested archive indicated by <paramref name="entry"/>.</param> /// <param name="entry">The entry within <paramref name="parentArchiveAccess"/> that indicates a nested archive.</param> /// <returns>An instance of <see cref="NestedCompressedArchiveAccess"/>, which provides access to the nested archive.</returns> /// <remarks>This wrapper type takes care of cleaning up any temporary files that may be required to access the nested archive. /// For example, some data streams used to access an archive are not navigable from the parent stream (GZIP).</remarks> public static NestedCompressedArchiveAccess Create(ICompressedArchiveAccess parentArchiveAccess, ICompressedArchiveEntry entry) { NestedCompressedArchiveAccess nestedCompressedArchive = null; var entryName = entry.Name; var nestedArchiveFormats = Path.GetExtension(entryName).GetCompressedArchiveFormatsFromFileExtension(); var nestedArchiveFormat = nestedArchiveFormats.FirstOrDefault(); if (nestedArchiveFormat.IsCompressedArchiveFormatSupported()) { TemporaryDirectory temporaryLocation = null; var entryData = parentArchiveAccess.OpenEntry(entry); if (FormatMustBeExtracted(parentArchiveAccess.Format) || FormatMustBeExtracted(nestedArchiveFormat)) { // We can't fully navigate the nested stream, so extract to disk, then proceed. temporaryLocation = new TemporaryDirectory(); var temporaryEntryFilePath = Path.Combine(temporaryLocation.Path, entryName); if (Directory.CreateDirectory(Path.GetDirectoryName(temporaryEntryFilePath)).Exists) { System.Diagnostics.Debug.WriteLine("Extracted entry " + entryName + " to " + temporaryLocation.Path); var fileStream = new FileStream(temporaryEntryFilePath, FileMode.Create, FileAccess.ReadWrite); entryData.CopyTo(fileStream); fileStream.Seek(0, SeekOrigin.Begin); entryData.Dispose(); entryData = fileStream; } } var compressedArchive = CompressedArchiveAccess.Open(entryData, nestedArchiveFormat, CompressedArchiveAccessMode.Read); nestedCompressedArchive = new NestedCompressedArchiveAccess(parentArchiveAccess, compressedArchive, temporaryLocation); nestedCompressedArchive.NestedArchiveFormats = nestedArchiveFormats; } return(nestedCompressedArchive); }
/// <summary> /// Creates an instance of <see cref="CompressedArchiveFileAccess"/>, depending on <paramref name="mode"/>. /// </summary> /// <param name="filePath">The absolute path for the compressed archive.</param> /// <param name="mode">The access mode to use for operations on the compressed archive.</param> /// <param name="implementation">If not <c>null</c>, use a specific implementation if possible. Otherwise, use default, or any.</param> /// <returns>An instance of <see cref="CompressedArchiveFileAccess"/> that provides access to the compressed archive located at <paramref name="filePath"/>.</returns> /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="filePath"/> is <c>null</c>.</exception> /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the file is accessed incorrectly for the given <paramref name="mode"/>.</exception> /// <exception cref="System.ArgumentException">Thrown if <paramref name="filePath"/> is empty or white space, contains invalid characters, or is otherwise invalid</exception> /// <exception cref="System.NotSupportedException">Thrown if <paramref name="filePath"/> refers to a non-file device, e.g. a COM port, et. al.</exception> /// <exception cref="System.IO.FileNotFoundException">Thrown if <paramref name="filePath"/> cannot be found.</exception> /// <exception cref="System.IO.IOException">Thrown if an I/O error occurs.</exception> /// <exception cref="System.Security.SecurityException">Thrown if the requested action on <paramref name="filePath"/> cannot be performed, e.g. no read / create access is granted, et. al.</exception> /// <exception cref="System.IO.DirectoryNotFoundException">Thrown if the directory cannot be found, e.g. an unavailable network drive forms part of the file path.</exception> /// <exception cref="System.UnauthorizedAccessException">Thrown if access is denied, e.g. read/write access requested for a read-only file or directory.</exception> /// <exception cref="System.IO.PathTooLongException">Thrown if <paramref name="filePath"/> is too long.</exception> /// <exception cref="System.InvalidOperationException">Thrown if it is not possible to create an instance of <see cref="CompressedArchiveFileAccess"/> from the file at <paramref name="filePath"/>, /// despite the file appearing to be valid.</exception> public static CompressedArchiveFileAccess Create(string filePath, CompressedArchiveAccessMode mode, CompressedArchiveAccessImplementation?implementation) { var fileMode = CompressedArchiveAccessModeToFileMode(mode); var fileAccess = CompressedArchiveAccessModeToFileAccess(mode); var fileName = Path.GetFileName(filePath); var successfullyAccessedFormats = new List <CompressedArchiveFormat>(); var fileStream = new FileStream(filePath, fileMode, fileAccess); var formats = filePath.GetCompressedArchiveFormatsFromFileName(); ICompressedArchiveAccess compressedArchiveAccess = null; #if OPEN_NESTED_FORMAT_IMMEDIATELY Stream stream = fileStream; foreach (var format in formats) { compressedArchiveAccess = Utility.CompressedArchiveAccess.Open(stream, format, mode); if (compressedArchiveAccess != null) { successfullyAccessedFormats.Add(format); if (!compressedArchiveAccess.IsArchive) { fileName = Path.GetFileNameWithoutExtension(fileName); var entry = compressedArchiveAccess.FindEntry(fileName); stream = entry == null ? null : compressedArchiveAccess.OpenEntry(entry); if (stream == null) { compressedArchiveAccess = null; break; } } } else { break; } } #else var format = formats.FirstOrDefault(); if (format != CompressedArchiveFormat.None) { compressedArchiveAccess = Utility.CompressedArchiveAccess.Open(fileStream, format, mode, implementation); } #endif if (compressedArchiveAccess == null) { var identifiedFormats = string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, formats); var successfullyCreatedFormats = string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, successfullyAccessedFormats); var failedFormats = string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, formats.Except(successfullyAccessedFormats)); var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.Strings.CompressedArchiveAccess_UnableToProcessError_Format, filePath, identifiedFormats, successfullyCreatedFormats, failedFormats); throw new InvalidOperationException(errorMessage); } var compressedArchiveFileAccess = new CompressedArchiveFileAccess(filePath, compressedArchiveAccess) { Stream = fileStream }; return(compressedArchiveFileAccess); }
/// <summary> /// List the contents of the given compressed archive. /// </summary> /// <param name="compressedArchiveAccess">An instance of <see cref="ICompressedArchiveAccess"/> whose contents are to be listed.</param> /// <param name="locationInArchive">A location relative to the root of the archive. The special values <c>null</c>, <c>string.Empty</c>, '\', '/', or '.' indicate the root. Otherwise, must end with a directory separator character.</param> /// <param name="includeContainers">If <c>true</c>, include entries that may contain other entries, such as other compressed archives and directories.</param> /// <param name="recurse">If <c>true</c>, list all contents from <paramref name="locationInArchive"/> and below, recursively. The contents of nested archives will also be listed.</param> /// <returns>The list of entries, which may include entries that could contain more items, depending on the value of <paramref name="includeContainers"/>. /// Entry names are always relative to <paramref name="compressedArchiveAccess"/>. Path separators will be normalized to forward slash.</returns> /// <remarks>NOTE: Large and / or deeply nested archives may incur performance and disk penalties. Use with care!</remarks> /// <exception cref="ArgumentNullException">Thrown if <paramref name="compressedArchiveAccess"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="locationInArchive"/> is malformed i.e. is not null or empty, or does not end with a directory separator character.</exception> /// <exception cref="FileNotFoundException">Thrown if <paramref name="locationInArchive"/> identifies a nested archive that cannot be located.</exception> public static IEnumerable <ICompressedArchiveEntry> ListEntries(this ICompressedArchiveAccess compressedArchiveAccess, string locationInArchive, bool includeContainers, bool recurse) { var entries = new List <ICompressedArchiveEntry>(ListEntriesInCompressedArchive(compressedArchiveAccess, locationInArchive, includeContainers || recurse)); if (recurse) { // Uses a simple queue to process nested contents in a breadth-first-ish fashion. // Nested archives are kept around as discovered so subsequent listings that may cause actual extraction to temporary // locations on disk is somewhat mitigated. var nestedCompressedArchives = new Dictionary <string, ICompressedArchiveAccess>(); var containers = new Queue <ICompressedArchiveEntry>(entries.Where(e => e.IsDirectory || e.Name.IsContainer())); while (containers.Any()) { var container = containers.Dequeue(); ICompressedArchiveAccess nestedCompressedArchiveAccess; string nestedArchiveRelativeLocation; var nestedAchiveLocation = container.Name.GetMostDeeplyNestedContainerLocation(out nestedArchiveRelativeLocation); if (nestedAchiveLocation != null) { if (!nestedCompressedArchives.TryGetValue(nestedAchiveLocation, out nestedCompressedArchiveAccess)) { var dontCare = string.Empty; nestedCompressedArchiveAccess = GetNestedCompressedArchive(compressedArchiveAccess, nestedAchiveLocation, ref dontCare); nestedCompressedArchives[nestedAchiveLocation] = nestedCompressedArchiveAccess; } } else { nestedCompressedArchiveAccess = compressedArchiveAccess; } var childEntries = ListEntriesInCompressedArchive(nestedCompressedArchiveAccess, nestedArchiveRelativeLocation, includeContainers: true).Select(e => e.MakeAbsoluteEntry(nestedAchiveLocation)); entries.AddRange(childEntries); var childContainers = childEntries.Where(e => e.IsDirectory || e.Name.IsContainer()); foreach (var childContainer in childContainers) { containers.Enqueue(childContainer); } } if (!includeContainers) { entries = entries.Where(e => !e.IsDirectory && !e.Name.IsContainer()).ToList(); } foreach (var nestedCompresedArchive in nestedCompressedArchives.Values) { nestedCompresedArchive.Dispose(); } } return(entries.OrderBy(e => e.Name)); }
private static NestedCompressedArchiveAccess GetNestedCompressedArchive(ICompressedArchiveAccess compressedArchiveAccess, string locationInArchive, ref string nestedArchiveLocation) { var nestedArchiveLocations = locationInArchive.GetNestedContainerLocations(); var entryName = nestedArchiveLocations.First(); if (string.IsNullOrEmpty(nestedArchiveLocation)) { nestedArchiveLocation = entryName + '/'; } else { nestedArchiveLocation += entryName + '/'; } var nestedArchiveEntry = compressedArchiveAccess.Entries.FirstOrDefault(e => e.Name.NormalizePathSeparators() == entryName); NestedCompressedArchiveAccess nestedCompressedArchive = null; if (nestedArchiveEntry != null) { nestedCompressedArchive = NestedCompressedArchiveAccess.Create(compressedArchiveAccess, nestedArchiveEntry); if (nestedCompressedArchive != null) { // If there is more than one nested archive format on the location, e.g. it's a .tar.gz, or if we did not consume the entire location // in the archive, then we need to recurse into this newly located archive to get to the ultimate nested archive. if ((nestedCompressedArchive.NestedArchiveFormats.Count() > 1) || (nestedArchiveLocations.Length > 1)) { locationInArchive = string.Join("/", nestedArchiveLocations, 1, nestedArchiveLocations.Length - 1) + '/'; if (locationInArchive.IsInNestedContainer()) { nestedCompressedArchive = GetNestedCompressedArchive(nestedCompressedArchive, locationInArchive, ref nestedArchiveLocation); } } } } return(nestedCompressedArchive); }
/// <summary> /// List the contents of the given compressed archive. /// </summary> /// <param name="compressedArchiveAccess">An instance of <see cref="ICompressedArchiveAccess"/> whose contents are to be listed.</param> /// <param name="locationInArchive">A location relative to the root of the archive. The special values <c>null</c>, <c>string.Empty</c>, '\', '/', or '.' indicate the root. Otherwise, must end with a directory separator character.</param> /// <param name="includeContainers">If <c>true</c>, include entries that may contain other entries, such as other compressed archives and directories.</param> /// <returns>The list of entries, which may include entries that could contain more items, depending on the value of <paramref name="includeContainers"/>.</returns> /// <remarks>NOTE: Entry names are always relative to <paramref name="compressedArchiveAccess"/>. Path separators will be normalized to forward slash.</remarks> /// <exception cref="ArgumentNullException">Thrown if <paramref name="compressedArchiveAccess"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="locationInArchive"/> is malformed i.e. is not null or empty, or does not end with a directory separator character.</exception> /// <exception cref="FileNotFoundException">Thrown if <paramref name="locationInArchive"/> identifies a nested archive that cannot be located.</exception> public static IEnumerable <ICompressedArchiveEntry> ListEntries(this ICompressedArchiveAccess compressedArchiveAccess, string locationInArchive, bool includeContainers) { return(compressedArchiveAccess.ListEntries(locationInArchive, includeContainers, recurse: false)); }
private NestedCompressedArchiveAccess(ICompressedArchiveAccess parentArchiveAccess, ICompressedArchiveAccess nestedArchiveAccess, TemporaryDirectory temporaryLocation) { ParentArchiveAccess = parentArchiveAccess; NestedAchiveAccess = nestedArchiveAccess; TemporaryLocation = temporaryLocation; }
private static IEnumerable <ICompressedArchiveEntry> ListEntriesInCompressedArchive(ICompressedArchiveAccess compressedArchiveAccess, string locationInArchive, bool includeContainers) { if (compressedArchiveAccess == null) { throw new ArgumentNullException("compressedArchiveAccess"); } if (!string.IsNullOrEmpty(locationInArchive) && (locationInArchive != ".")) { if ((locationInArchive.Last() != '\\') && (locationInArchive.Last() != '/')) { throw new ArgumentException("locationInArchive"); } } // If location is within a nested archive, do what is necessary to get access to the nested archive from the supplied archive. var originalArchiveAccess = compressedArchiveAccess; locationInArchive = locationInArchive.NormalizePathSeparators(); var normalizedLocationInArchive = locationInArchive; var locationIsInNestedContainer = locationInArchive.IsInNestedContainer(); if (locationIsInNestedContainer) { normalizedLocationInArchive = string.Empty; compressedArchiveAccess = GetNestedCompressedArchive(compressedArchiveAccess, locationInArchive, ref normalizedLocationInArchive); if (compressedArchiveAccess == null) { var message = string.Format(CultureInfo.CurrentCulture, Resources.Strings.CompressedArchiveAccess_NestedArchiveNotFound, locationInArchive); throw new FileNotFoundException(message, locationInArchive); } } var entries = compressedArchiveAccess.Entries.Select(e => new NormalizedCompressedArchiveEntry(e, normalizedLocationInArchive, locationIsInNestedContainer)); if (!includeContainers) { entries = entries.Where(e => !e.IsDirectory && !e.Name.IsContainer()); } if (locationInArchive.IsRootLocation()) { var virtualEntriesToAdd = new List <NormalizedCompressedArchiveEntry>(); if (includeContainers) { // When listing, there are cases in which there may no direct entry for a directory. Add one. var entryNames = entries.Select(e => e.Name).ToList(); var nestedEntryNames = entryNames.Select(n => n.GetArchiveRelativePathSegments()).Where(s => s.Length > 1).Select(s => s.First() + '/').Distinct().ToList(); nestedEntryNames.RemoveAll(n => entryNames.Contains(n)); virtualEntriesToAdd.AddRange(nestedEntryNames.Select(n => new NormalizedCompressedArchiveEntry(n, locationInArchive, locationIsInNestedContainer))); } entries = entries.Where(e => e.Name.GetArchiveRelativePathSegments().Length < 2); entries = entries.Concat(virtualEntriesToAdd); } else { entries = entries.Where(e => e.Name.StartsWith(locationInArchive, PathComparer.DefaultPolicy) && (e.Name.Length > locationInArchive.Length)); entries = entries.Where(e => e.Name.Substring(locationInArchive.Length + 1).GetArchiveRelativePathSegments().Length < 2); } if (!object.ReferenceEquals(compressedArchiveAccess, originalArchiveAccess)) { compressedArchiveAccess.Dispose(); } return(entries); }
/// <summary> /// Initializes a new instance of <see cref="CompressedArchiveFileAccess"/>. /// </summary> /// <param name="filePath">The absolute path to the compressed archive file.</param> /// <param name="compressedArchiveAccess">The compressed archive to wrap.</param> private CompressedArchiveFileAccess(string filePath, ICompressedArchiveAccess compressedArchiveAccess) { RootLocation = filePath; CompressedArchiveAccess = compressedArchiveAccess; }
public static TestCompressedArchiveAccess GetFromCompressedArchiveFileAccess(ICompressedArchiveAccess archive) { var fileAccessArchiveType = typeof(INTV.Shared.Utility.CompressedArchiveAccess).Assembly.GetType("INTV.Shared.Utility.CompressedArchiveAccess+CompressedArchiveFileAccess"); var instanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; var property = fileAccessArchiveType.GetProperty("CompressedArchiveAccess", instanceFlags); var testArchive = property.GetValue(archive) as TestCompressedArchiveAccess; return(testArchive); }