Skip to content

Commit 60575d1

Browse files
committed
organized files, worked on tar.gz support, filter and flatten support
1 parent c246a4b commit 60575d1

File tree

10 files changed

+162
-170
lines changed

10 files changed

+162
-170
lines changed

src/CompressArchiveCommand.cs renamed to src/Cmdlets/CompressArchiveCommand.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,25 @@ private enum ParameterSet
5555
[NotNull]
5656
public string? DestinationPath { get; set; }
5757

58-
[Parameter()]
58+
[Parameter]
5959
public WriteMode WriteMode { get; set; }
6060

61-
[Parameter()]
61+
[Parameter]
6262
public SwitchParameter PassThru { get; set; }
6363

64-
[Parameter()]
64+
[Parameter]
6565
[ValidateNotNullOrEmpty]
6666
public CompressionLevel CompressionLevel { get; set; }
6767

68-
[Parameter()]
69-
public ArchiveFormat? Format { get; set; } = null;
68+
[Parameter]
69+
public ArchiveFormat? Format { get; set; }
70+
71+
[Parameter]
72+
[ValidateNotNullOrEmpty]
73+
public string? Filter { get; set; }
74+
75+
[Parameter]
76+
public SwitchParameter Flatten { get; set; }
7077

7178
private readonly PathHelper _pathHelper;
7279

@@ -161,6 +168,8 @@ protected override void EndProcessing()
161168
// Get archive entries
162169
// If a path causes an exception (e.g., SecurityException), _pathHelper should handle it
163170
Debug.Assert(_paths is not null);
171+
_pathHelper.Flatten = Flatten;
172+
_pathHelper.Filter = Filter;
164173
List<ArchiveAddition> archiveAdditions = _pathHelper.GetArchiveAdditions(_paths);
165174

166175
// Remove references to _paths, Path, and LiteralPath to free up memory

src/ExpandArchiveCommand.cs renamed to src/Cmdlets/ExpandArchiveCommand.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ private enum ParameterSet {
4646

4747
private PathHelper _pathHelper;
4848

49-
private System.IO.FileSystemInfo? _destinationPathInfo;
50-
5149
private bool _didCreateOutput;
5250

5351
private string? _sourcePath;
@@ -56,9 +54,7 @@ private enum ParameterSet {
5654

5755
public ExpandArchiveCommand()
5856
{
59-
_didCreateOutput = false;
6057
_pathHelper = new PathHelper(cmdlet: this);
61-
_destinationPathInfo = null;
6258
}
6359

6460
protected override void BeginProcessing()
@@ -189,10 +185,6 @@ private void ProcessArchiveEntry(IEntry entry)
189185
postExpandPath = postExpandPath.Remove(postExpandPath.Length - 1);
190186
}
191187

192-
// Notify the user that we are expanding the entry
193-
//var expandingEntryMsg = string.Format(Messages.ExpandingEntryMessage, entry.Name, postExpandPath);
194-
//WriteObject(expandingEntryMsg);
195-
196188
// If the entry name is invalid, write a non-terminating error and stop processing the entry
197189
if (IsPathInvalid(postExpandPath))
198190
{
File renamed without changes.

src/GzipArchive.cs renamed to src/Formats/GzipArchive.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ namespace Microsoft.PowerShell.Archive
1212
{
1313
internal class GzipArchive : IArchive
1414
{
15-
private bool _disposedValue;
15+
protected bool _disposedValue;
1616

17-
private readonly ArchiveMode _mode;
17+
protected readonly ArchiveMode _mode;
1818

19-
private readonly string _path;
19+
protected readonly string _path;
2020

21-
private readonly FileStream _fileStream;
21+
protected readonly FileStream _fileStream;
2222

23-
private readonly CompressionLevel _compressionLevel;
23+
protected readonly CompressionLevel _compressionLevel;
2424

2525
private bool _addedFile;
2626

27-
private bool _didCallGetNextEntry;
27+
protected bool _didCallGetNextEntry;
2828

2929
ArchiveMode IArchive.Mode => _mode;
3030

@@ -38,7 +38,7 @@ public GzipArchive(string path, ArchiveMode mode, FileStream fileStream, Compres
3838
_compressionLevel = compressionLevel;
3939
}
4040

41-
void IArchive.AddFileSystemEntry(ArchiveAddition entry)
41+
public virtual void AddFileSystemEntry(ArchiveAddition entry)
4242
{
4343
if (_mode == ArchiveMode.Extract)
4444
{

src/TarArchive.cs renamed to src/Formats/TarArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
3838
_fileStream = fileStream;
3939
}
4040

41-
void IArchive.AddFileSystemEntry(ArchiveAddition entry)
41+
public void AddFileSystemEntry(ArchiveAddition entry)
4242
{
4343
if (_mode == ArchiveMode.Extract)
4444
{

src/Formats/TarGzArchive.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Formats.Tar;
7+
using System.IO;
8+
using System.IO.Compression;
9+
using System.Diagnostics;
10+
11+
namespace Microsoft.PowerShell.Archive
12+
{
13+
internal class TarGzArchive : GzipArchive
14+
{
15+
16+
// Use a tar archive because .tar.gz file is a compressed tar file
17+
private TarArchive? _tarArchive;
18+
19+
private string? _tarFilePath;
20+
21+
private FileStream? _tarFileStream;
22+
23+
public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel) : base(path, mode, fileStream, compressionLevel)
24+
{
25+
}
26+
27+
public override void AddFileSystemEntry(ArchiveAddition entry)
28+
{
29+
if (_mode == ArchiveMode.Extract || _mode == ArchiveMode.Update) {
30+
throw new ArgumentException("Adding entries to the archive is not supported in extract or update mode");
31+
}
32+
33+
if (_tarArchive is null)
34+
{
35+
var outputDirectory = Path.GetDirectoryName(_path);
36+
var tarFilename = Path.GetRandomFileName();
37+
_tarFilePath = Path.Combine(outputDirectory, tarFilename);
38+
_tarFileStream = new FileStream(_tarFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
39+
_tarArchive = new TarArchive(_tarFilePath, ArchiveMode.Create, _tarFileStream);
40+
41+
}
42+
_tarArchive.AddFileSystemEntry(entry);
43+
}
44+
45+
protected override void Dispose(bool disposing)
46+
{
47+
if (!_disposedValue)
48+
{
49+
if (disposing)
50+
{
51+
// TODO: dispose managed state (managed objects)
52+
_fileStream.Dispose();
53+
CompressArchive();
54+
}
55+
56+
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
57+
// TODO: set large fields to null
58+
_disposedValue = true;
59+
}
60+
}
61+
62+
// Performs gzip compression on _path
63+
private void CompressArchive() {
64+
Debug.Assert(_tarFileStream is not null);
65+
_tarFileStream.Position = 0;
66+
using var gzipCompressor = new GZipStream(_fileStream, _compressionLevel, true);
67+
_tarFileStream.CopyTo(gzipCompressor);
68+
}
69+
}
70+
}
File renamed without changes.

src/IArchive.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@
77

88
namespace Microsoft.PowerShell.Archive
99
{
10-
internal interface IArchive: IDisposable
10+
interface IArchive: IDisposable
1111
{
1212
// Get what mode the archive is in
13-
internal ArchiveMode Mode { get; }
13+
public ArchiveMode Mode { get; }
1414

1515
// Get the fully qualified path of the archive
16-
internal string Path { get; }
16+
public string Path { get; }
1717

1818
// Add a file or folder to the archive. The entry name of the added item in the
1919
// will be ArchiveEntry.Name.
2020
// Throws an exception if the archive is in read mode.
21-
internal void AddFileSystemEntry(ArchiveAddition entry);
21+
public void AddFileSystemEntry(ArchiveAddition entry);
2222

23-
internal IEntry? GetNextEntry();
23+
public IEntry? GetNextEntry();
2424

2525
// Does the archive have only a top-level directory?
26-
internal bool HasTopLevelDirectory();
26+
public bool HasTopLevelDirectory();
2727
}
2828
}

src/PathHelper.cs

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,29 @@ internal class PathHelper
1717

1818
private const string FileSystemProviderName = "FileSystem";
1919

20+
internal bool Flatten { get; set; }
21+
22+
internal string? Filter { get; set; }
23+
24+
internal WildcardPattern? _wildCardPattern;
25+
2026
internal PathHelper(PSCmdlet cmdlet)
2127
{
2228
_cmdlet = cmdlet;
2329
}
2430

2531
internal List<ArchiveAddition> GetArchiveAdditions(HashSet<string> fullyQualifiedPaths)
2632
{
33+
if (Filter is not null) {
34+
_wildCardPattern = new WildcardPattern(Filter);
35+
}
2736
List<ArchiveAddition> archiveAdditions = new List<ArchiveAddition>(fullyQualifiedPaths.Count);
2837
foreach (var path in fullyQualifiedPaths)
2938
{
3039
// Assume each path is valid, fully qualified, and existing
3140
Debug.Assert(Path.Exists(path));
3241
Debug.Assert(Path.IsPathFullyQualified(path));
33-
AddAdditionForFullyQualifiedPath(path, archiveAdditions);
42+
AddAdditionForFullyQualifiedPath(path, archiveAdditions, entryName: null, parentMatchesFilter: false);
3443
}
3544
return archiveAdditions;
3645
}
@@ -41,7 +50,7 @@ internal List<ArchiveAddition> GetArchiveAdditions(HashSet<string> fullyQualifie
4150
/// <param name="path">The fully qualified path</param>
4251
/// <param name="additions">The list where to add the ArchiveAddition object for the path</param>
4352
/// <param name="shouldPreservePathStructure">If true, relative path structure will be preserved. If false, relative path structure will NOT be preserved.</param>
44-
private void AddAdditionForFullyQualifiedPath(string path, List<ArchiveAddition> additions)
53+
private void AddAdditionForFullyQualifiedPath(string path, List<ArchiveAddition> additions, string? entryName, bool parentMatchesFilter)
4554
{
4655
Debug.Assert(Path.Exists(path));
4756
FileSystemInfo fileSystemInfo;
@@ -61,15 +70,38 @@ private void AddAdditionForFullyQualifiedPath(string path, List<ArchiveAddition>
6170
fileSystemInfo = new FileInfo(path);
6271
}
6372

64-
// Get the entry name of the file or directory in the archive
65-
// The cmdlet will preserve the directory structure as long as the path is relative to the working directory
66-
var entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure);
67-
additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
73+
bool doesMatchFilter = true;
74+
if (!parentMatchesFilter && _wildCardPattern is not null) {
75+
doesMatchFilter = _wildCardPattern.IsMatch(fileSystemInfo.Name);
76+
}
77+
78+
// if entryName, then set it as the entry name of the file or directory in the archive
79+
// The entry name will preserve the directory structure as long as the path is relative to the working directory
80+
if (entryName is null) {
81+
entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure);
82+
}
83+
84+
85+
// Number of elements in additions before adding this item and its descendents if it is a directory
86+
int initialAdditions = additions.Count;
6887

6988
// Recurse through the child items and add them to additions
70-
if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && fileSystemInfo is DirectoryInfo directoryInfo) {
71-
AddDescendentEntries(directoryInfo: directoryInfo, additions: additions, shouldPreservePathStructure: doesPreservePathStructure);
89+
if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && fileSystemInfo is DirectoryInfo directoryInfo)
90+
{
91+
AddDescendentEntries(directoryInfo, additions, doesMatchFilter);
7292
}
93+
94+
// Number of elements in additions after adding this item's descendents (if directory)
95+
int finalAdditions = additions.Count;
96+
97+
// If the item being added is a file, finalAdditions - initialAdditions = 0
98+
// If the item being added is a directory and does not have any descendent files that match the filter, finalAdditions - initialAdditions = 0
99+
// If the item being added is a directory and has descendent files that match the filter, finalAdditions > initialAdditions
100+
101+
if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0)) {
102+
additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
103+
}
104+
73105
}
74106

75107
/// <summary>
@@ -78,28 +110,41 @@ private void AddAdditionForFullyQualifiedPath(string path, List<ArchiveAddition>
78110
/// <param name="path">A fully qualifed path referring to a directory</param>
79111
/// <param name="additions">Where the ArchiveAddtion object for each child item of the directory will be added</param>
80112
/// <param name="shouldPreservePathStructure">See above</param>
81-
private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List<ArchiveAddition> additions, bool shouldPreservePathStructure)
113+
private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List<ArchiveAddition> additions, bool parentMatchesFilter)
82114
{
83115
try
84116
{
85117
// pathPrefix is used to construct the entry names of the descendents of the directory
86118
var pathPrefix = GetPrefixForPath(directoryInfo: directoryInfo);
87-
foreach (var childFileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
119+
// If the parent directory matches the filter, then we don't have to check if each individual descendent of the directory
120+
// matches the filter.
121+
// This reduces the total number of method calls
122+
SearchOption searchOption = parentMatchesFilter ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
123+
foreach (var childFileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", searchOption))
88124
{
89125
string entryName;
90-
// If the cmdlet should preserve the path structure, then use the relative path
91-
if (shouldPreservePathStructure)
126+
if (Flatten)
92127
{
93-
entryName = GetEntryName(childFileSystemInfo, out bool doesPreservePathStructure);
94-
Debug.Assert(doesPreservePathStructure);
128+
entryName = childFileSystemInfo.Name;
129+
} else
130+
{
131+
entryName = GetEntryNameUsingPrefix(path: childFileSystemInfo.FullName, prefix: pathPrefix);
95132
}
96-
// Otherwise, get the entry name using the prefix
133+
134+
135+
// Add an entry for each descendent of the directory
136+
if (parentMatchesFilter)
137+
{
138+
// If the parent directory matches the filter, all its contents are included in the archive
139+
// Just add the entry for each child without needing to check whether the child matches the filter
140+
additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: childFileSystemInfo));
141+
}
97142
else
98143
{
99-
entryName = GetEntryNameUsingPrefix(path: childFileSystemInfo.FullName, prefix: pathPrefix);
144+
// If the parent directory does not match the filter, we want to call this function
145+
// because this function will check if the name of the child matches the filter and if so, will add it
146+
AddAdditionForFullyQualifiedPath(childFileSystemInfo.FullName, additions, entryName, parentMatchesFilter: false);
100147
}
101-
// Add an entry for each descendent of the directory
102-
additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: childFileSystemInfo));
103148
}
104149
}
105150
// Write a non-terminating error if a securityException occurs
@@ -122,7 +167,7 @@ private string GetEntryName(FileSystemInfo fileSystemInfo, out bool doesPreserve
122167
string entryName;
123168
doesPreservePathStructure = false;
124169
// If the path is relative to the current working directory, return the relative path as name
125-
if (TryGetPathRelativeToCurrentWorkingDirectory(path: fileSystemInfo.FullName, out var relativePath))
170+
if (!Flatten && TryGetPathRelativeToCurrentWorkingDirectory(path: fileSystemInfo.FullName, out var relativePath))
126171
{
127172
Debug.Assert(relativePath is not null);
128173
doesPreservePathStructure = true;

0 commit comments

Comments
 (0)