mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2025-01-20 07:19:18 +01:00
CA-375900: Prepend \\?\
to file paths when creating streams for archive generation (#3184)
* CA-375900: Prepend `//?/` to file paths when creating streams for archive generation The string works to enable creation of files with paths larger than 260 characters. * CA-375900: Add directory support and rename utility method * Fix whitespace in `ArchiveWriterTest` * CA-375900: Explicitly enumerate files and directories in `ArchiveWriter` `Directory.GetFiles` and `Directory.GetDirectories` do not enumerate if paths are longer than 260, even when prepended with `//?/`. * CA-375900: Add long path tests to `ArchiveWriter` * CA-375900: Add long path tests to `ArchiveIterator` * CA-375900: Ensure files are added to folders in archive * Use a recursive method to add directories and files to archive in `ArchiveIterator` Also improves progress reporting by basing it on directory count * Fix typos * Expand `ArchiveWriterTests` to cover all combinations of directory and path lengths * Ensure that directories used in recursive `Directory.Delete` calls are using long path format If files in the directory exceed the 260 character limit, the calls will fail * Expand `ArchiveIteratorTests` to cover all combinations of directory and path lengths * Ensure relative path name removes `rootPath` * Fix typo * Do not use long paths when importing OVFs The import uses `DiscUtils` which cannot handle paths prepended with `//?/` * Remove use of `ToLongWindowsPath` within appliance export This partially reverts commit 819425855c56c14b937849714b359003465bd2f4. * Refactoring and some corrections. Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com> Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com> Co-authored-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
This commit is contained in:
parent
9470e20808
commit
86fa2f6abf
@ -32,6 +32,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using XenCenterLib;
|
||||
using XenCenterLib.Archive;
|
||||
|
||||
namespace XenAdminTests.ArchiveTests
|
||||
@ -55,7 +56,7 @@ namespace XenAdminTests.ArchiveTests
|
||||
disposed = false;
|
||||
}
|
||||
}
|
||||
public string CurrentFileNameReturn { private get; set; }
|
||||
public string CurrentFileNameReturn { get; set; }
|
||||
public long CurrentFileSizeReturn { private get; set; }
|
||||
public DateTime ModTimeReturn { private get; set; }
|
||||
public bool IsDirectoryReturn { private get; set; }
|
||||
@ -97,10 +98,10 @@ namespace XenAdminTests.ArchiveTests
|
||||
|
||||
public override string CurrentFileName()
|
||||
{
|
||||
if (String.IsNullOrEmpty(CurrentFileNameReturn))
|
||||
if (string.IsNullOrEmpty(CurrentFileNameReturn))
|
||||
return CurrentFileNameReturn;
|
||||
|
||||
return NumberOfCallsLeftReturn + CurrentFileNameReturn;
|
||||
return CurrentFileNameReturn + NumberOfCallsLeftReturn;
|
||||
}
|
||||
|
||||
public override long CurrentFileSize()
|
||||
@ -121,19 +122,15 @@ namespace XenAdminTests.ArchiveTests
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if( !disposed )
|
||||
{
|
||||
if( disposing )
|
||||
{
|
||||
if(extractedFile != null)
|
||||
extractedFile.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (!disposed && disposing)
|
||||
extractedFile?.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private ArchiveIteratorFake fakeIterator;
|
||||
private string tempPath;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
@ -150,66 +147,101 @@ namespace XenAdminTests.ArchiveTests
|
||||
[SetUp]
|
||||
public void TestSetup()
|
||||
{
|
||||
tempPath = null;
|
||||
fakeIterator.Reset();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TestTearDown()
|
||||
{
|
||||
if (Directory.Exists(tempPath))
|
||||
Directory.Delete(tempPath, true);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void AnExceptionIsThrownForNullArgumentWhenCallingExtractAllContents()
|
||||
public void TestExtractToNullDestinationPath()
|
||||
{
|
||||
Assert.Throws(typeof(ArgumentNullException), () => fakeIterator.ExtractAllContents(null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnExceptionIsThrownForANullFileNameWhenCallingExtractAllContents()
|
||||
public void TestExtractNullFile()
|
||||
{
|
||||
fakeIterator.CurrentFileNameReturn = null;
|
||||
Assert.Throws(typeof(ArgumentNullException), () => fakeIterator.ExtractAllContents(Path.GetTempPath()));
|
||||
Assert.Throws(typeof(NullReferenceException), () => fakeIterator.ExtractAllContents(Path.GetTempPath()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VerifyAFileIsWrittenWhenCallingExtractAllContents()
|
||||
|
||||
[TestCase(true, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public void TestExtractFile(bool longDirectoryPath, bool longFilePath)
|
||||
{
|
||||
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
|
||||
//2 was removed for the combining slash between tempPath and dir, and the combining slash between dir and filename
|
||||
var dir = new string('A', dirCharNumber);
|
||||
var fileCharNumber = (longFilePath ? 260 : 259) - Path.Combine(tempPath, dir).Length - 2;
|
||||
//2 was removed for the combining slash between the full dir path and filename, and the NumberOfCallsLeftReturn
|
||||
var fileName = new string('B', fileCharNumber);
|
||||
|
||||
const int numberOfFiles = 3;
|
||||
fakeIterator.NumberOfCallsLeftReturn = numberOfFiles;
|
||||
fakeIterator.CurrentFileNameReturn = Path.Combine(dir, fileName);
|
||||
fakeIterator.ExtractAllContents(tempPath);
|
||||
|
||||
//Test file has been created
|
||||
string targetFile = Path.Combine(tempPath, fakeIterator.CurrentFileName());
|
||||
Assert.IsTrue(File.Exists(targetFile), "File Exists");
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
string targetFile = Path.Combine(tempPath, fakeIterator.CurrentFileNameReturn + i);
|
||||
|
||||
Assert.IsTrue(File.ReadAllBytes(targetFile).Length > 1, "File length > 1");
|
||||
if (longDirectoryPath || longFilePath)
|
||||
targetFile = StringUtility.ToLongWindowsPathUnchecked(targetFile);
|
||||
|
||||
Assert.IsTrue(File.Exists(targetFile), "File should exist");
|
||||
Assert.IsNotEmpty(File.ReadAllBytes(targetFile), "File should not be empty");
|
||||
|
||||
Assert.IsFalse((File.GetAttributes(targetFile) & FileAttributes.Directory) == FileAttributes.Directory,
|
||||
"It should not have directory attributes");
|
||||
}
|
||||
|
||||
//Check recursively that there are only the correct number of files
|
||||
Assert.IsTrue(Directory.GetFiles(tempPath, "*.*", SearchOption.AllDirectories).Length == numberOfFiles, "File number is correct");
|
||||
var actualFileNumber = Directory.GetFiles(tempPath, "*.*", SearchOption.AllDirectories).Length;
|
||||
Assert.AreEqual(numberOfFiles, actualFileNumber, $"There should be {numberOfFiles}");
|
||||
|
||||
Assert.IsFalse((File.GetAttributes(targetFile) & FileAttributes.Directory) == FileAttributes.Directory, "Is not a dir");
|
||||
|
||||
Directory.Delete(tempPath,true);
|
||||
if (longDirectoryPath || longFilePath)
|
||||
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VerifyADirectoryIsWrittenWhenCallingExtractAllContents()
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestExtractDirectory(bool longDirectoryPath)
|
||||
{
|
||||
string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
|
||||
//2 was removed for the combining slash between tempPath and dir, and the NumberOfCallsLeftReturn
|
||||
var dir = new string('A', dirCharNumber);
|
||||
|
||||
fakeIterator.IsDirectoryReturn = true;
|
||||
fakeIterator.CurrentFileNameReturn = "FakePath" + Path.DirectorySeparatorChar;
|
||||
fakeIterator.CurrentFileNameReturn = dir;
|
||||
fakeIterator.ExtractAllContents(tempPath);
|
||||
|
||||
//Test file has been created
|
||||
string targetPath = Path.Combine(tempPath, fakeIterator.CurrentFileName());
|
||||
Assert.IsFalse(File.Exists(targetPath), "No files exist");
|
||||
Assert.IsTrue(Directory.Exists(targetPath), "Directories exist");
|
||||
|
||||
//No files - just a directory
|
||||
Assert.IsTrue(Directory.GetFiles(tempPath).Length < 1, "No file in the directory" );
|
||||
if (longDirectoryPath)
|
||||
targetPath = StringUtility.ToLongWindowsPathUnchecked(targetPath);
|
||||
|
||||
//Check it's a directory
|
||||
Assert.IsTrue((File.GetAttributes(targetPath) & FileAttributes.Directory) == FileAttributes.Directory, "Has directory attributes");
|
||||
Assert.IsFalse(File.Exists(targetPath), "Files should not exist");
|
||||
Assert.IsEmpty(Directory.GetFiles(tempPath), "Directory should not have files");
|
||||
|
||||
Directory.Delete(tempPath, true);
|
||||
Assert.IsTrue(Directory.Exists(targetPath), "Directory should exist");
|
||||
Assert.IsTrue((File.GetAttributes(targetPath) & FileAttributes.Directory) == FileAttributes.Directory,
|
||||
"It should have directory attributes");
|
||||
|
||||
if (longDirectoryPath)
|
||||
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using XenCenterLib;
|
||||
using XenCenterLib.Archive;
|
||||
|
||||
namespace XenAdminTests.ArchiveTests
|
||||
@ -68,18 +69,18 @@ namespace XenAdminTests.ArchiveTests
|
||||
{
|
||||
if (AddedStreamData != null)
|
||||
{
|
||||
foreach (Stream stream in AddedStreamData)
|
||||
foreach (Stream stream in AddedStreamData)
|
||||
{
|
||||
if( stream != null )
|
||||
if (stream != null)
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
|
||||
public override void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
|
||||
{
|
||||
disposed = false;
|
||||
AddedStreamData.Add(filetoAdd);
|
||||
AddedStreamData.Add(fileToAdd);
|
||||
AddedFileNameData.Add(fileName);
|
||||
AddedDates.Add(modificationTime);
|
||||
}
|
||||
@ -93,9 +94,9 @@ namespace XenAdminTests.ArchiveTests
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if(disposing)
|
||||
if (disposing)
|
||||
{
|
||||
if( !disposed )
|
||||
if (!disposed)
|
||||
{
|
||||
DisposeStreamList();
|
||||
}
|
||||
@ -136,20 +137,10 @@ namespace XenAdminTests.ArchiveTests
|
||||
Assert.AreEqual(1, fakeWriter.AddedDates.Count);
|
||||
Assert.AreEqual(fileName, fakeWriter.AddedFileNameData[0], "File name");
|
||||
Assert.IsTrue(fakeWriter.AddedStreamData[0].Length == 14, "Stream has data");
|
||||
AssertCurrentDateIsPlausible(fakeWriter.AddedDates[0]);
|
||||
Assert.That(fakeWriter.AddedDates[0], Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertCurrentDateIsPlausible(DateTime currentDate)
|
||||
{
|
||||
//If this is failing check that the number of seconds is enough
|
||||
const double seconds = 5.0;
|
||||
DateTime maxDate = DateTime.Now.AddSeconds(seconds);
|
||||
DateTime minDate = DateTime.Now.AddSeconds(-1.0 * seconds);
|
||||
Assert.IsTrue(currentDate > minDate, "Date is > minimum");
|
||||
Assert.IsTrue(currentDate < maxDate, "Date is < maximum");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DatelessAddDirectoryCallsImplementation()
|
||||
{
|
||||
@ -164,7 +155,7 @@ namespace XenAdminTests.ArchiveTests
|
||||
Assert.AreEqual(0, fakeWriter.AddedStreamData.Count);
|
||||
Assert.AreEqual(totalAdded, fakeWriter.AddedDates.Count);
|
||||
Assert.AreEqual(dirName, fakeWriter.AddedFileNameData[0], "File name");
|
||||
AssertCurrentDateIsPlausible(fakeWriter.AddedDates[0]);
|
||||
Assert.That(fakeWriter.AddedDates[0], Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -173,6 +164,96 @@ namespace XenAdminTests.ArchiveTests
|
||||
Assert.Throws(typeof(FileNotFoundException), () => fakeWriter.CreateArchive("Yellow brick road - not a path!"));
|
||||
}
|
||||
|
||||
[TestCase(true, true)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public void CreateArchiveWithLongPath(bool longDirectoryPath, bool longFilePath)
|
||||
{
|
||||
//set up the path to zip
|
||||
var zipPath = PopulateLongPathArchive(true, longDirectoryPath, longFilePath, out var addedData);
|
||||
|
||||
fakeWriter.CreateArchive(zipPath);
|
||||
|
||||
foreach (var datum in addedData)
|
||||
Assert.Contains(datum, fakeWriter.AddedFileNameData);
|
||||
|
||||
// 2 folders and one file
|
||||
Assert.AreEqual(addedData.Count, fakeWriter.AddedFileNameData.Count);
|
||||
|
||||
//clean up: we need to ensure we're deleting the folder
|
||||
if (longDirectoryPath || longFilePath)
|
||||
zipPath = StringUtility.ToLongWindowsPathUnchecked(zipPath);
|
||||
|
||||
Directory.Delete(zipPath, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateArchiveWithLongPath_PathTooLong()
|
||||
{
|
||||
//! N.B.: If this test fails it might be because the project has moved to a version of .NET Core
|
||||
//! that does not require calls to `StringUtils.ToLongWindowsPath`. Please review its uses
|
||||
//! and remove it from the codebase if possible.
|
||||
|
||||
// this test ensures PopulateLongPathArchive's correctness
|
||||
// since CreateArchiveWithLongPath depends on it
|
||||
|
||||
Assert.DoesNotThrow(() => PopulateLongPathArchive(false, false, false, out _));
|
||||
Assert.Throws<DirectoryNotFoundException>(() => PopulateLongPathArchive(false, false, true, out _));
|
||||
Assert.Throws<PathTooLongException>(() => PopulateLongPathArchive(false, true, true, out _));
|
||||
Assert.Throws<PathTooLongException>(() => PopulateLongPathArchive(false, true, false, out _));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up method creating a directory containing 2 subdirectories one of which has a file
|
||||
/// </summary>
|
||||
/// <param name="createValidPaths">set to true to ensure folders and files are prepended with \\?\</param>
|
||||
/// <returns>the path to the folder</returns>
|
||||
private string PopulateLongPathArchive(bool createValidPaths, bool longDirectoryPath, bool longFilePath, out List<string> addedData)
|
||||
{
|
||||
var zipPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(zipPath);
|
||||
|
||||
var dirCharNumber1 = (longDirectoryPath ? 248 : 247) - zipPath.Length - 2;
|
||||
//2 was removed for the combining slash between tempPath and dir, and the first character
|
||||
var relativeDirectoryPath1 = 0 + new string('A', dirCharNumber1);
|
||||
|
||||
var dirCharNumber2 = (longDirectoryPath ? 248 : 247) - zipPath.Length - 3;
|
||||
//3 was removed for the combining slash between zipPath and dir, the first character,
|
||||
//and the combining slash between dir and filename
|
||||
var relativeDirectoryPath2 = 1 + new string('A', dirCharNumber2);
|
||||
|
||||
var fileCharNumber = (longFilePath ? 260 : 259) - Path.Combine(zipPath, relativeDirectoryPath2).Length - 1;
|
||||
//1 was removed for the combining slash between the full dir path and filename
|
||||
var fileName = new string('B', fileCharNumber);
|
||||
var relativeFilePath = Path.Combine(relativeDirectoryPath2, fileName);
|
||||
|
||||
addedData = new List<string>
|
||||
{
|
||||
relativeDirectoryPath1.Replace(@"\", "/"),
|
||||
relativeDirectoryPath2.Replace(@"\", "/"),
|
||||
relativeFilePath.Replace(@"\", "/")
|
||||
};
|
||||
|
||||
var directoryPath1 = Path.Combine(zipPath, relativeDirectoryPath1);
|
||||
var directoryPath2 = Path.Combine(zipPath, relativeDirectoryPath2);
|
||||
var filePath = Path.Combine(directoryPath2, fileName);
|
||||
|
||||
if (createValidPaths)
|
||||
{
|
||||
directoryPath1 = StringUtility.ToLongWindowsPathUnchecked(directoryPath1);
|
||||
directoryPath2 = StringUtility.ToLongWindowsPathUnchecked(directoryPath2);
|
||||
filePath = StringUtility.ToLongWindowsPathUnchecked(filePath);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(directoryPath1);
|
||||
Directory.CreateDirectory(directoryPath2);
|
||||
File.WriteAllText(filePath, "Hello, World!");
|
||||
|
||||
|
||||
return zipPath;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateArchiveWorksWithValidDirectoryStructure()
|
||||
{
|
||||
@ -184,17 +265,17 @@ namespace XenAdminTests.ArchiveTests
|
||||
{
|
||||
string subfolder = Path.Combine(tempPath, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(subfolder);
|
||||
CreateFiles( subfolder, i);
|
||||
CreateFiles(subfolder, i);
|
||||
}
|
||||
|
||||
fakeWriter.CreateArchive(tempPath);
|
||||
|
||||
Assert.AreEqual(12, fakeWriter.AddedDates.Count );
|
||||
Assert.AreEqual(12, fakeWriter.AddedDates.Count);
|
||||
Assert.AreEqual(12, fakeWriter.AddedFileNameData.Count);
|
||||
Assert.AreEqual(8, fakeWriter.AddedStreamData.Count);
|
||||
|
||||
foreach( DateTime date in fakeWriter.AddedDates )
|
||||
AssertCurrentDateIsPlausible(date);
|
||||
|
||||
foreach (DateTime date in fakeWriter.AddedDates)
|
||||
Assert.That(date, Is.EqualTo(DateTime.Now).Within(TimeSpan.FromSeconds(5)));
|
||||
|
||||
foreach (string name in fakeWriter.AddedFileNameData)
|
||||
Assert.AreEqual(-1, name.IndexOfAny(@":\".ToArray()), "Unwanted chars found in path");
|
||||
@ -206,7 +287,7 @@ namespace XenAdminTests.ArchiveTests
|
||||
{
|
||||
for (int i = 0; i < numberOfFiles; i++)
|
||||
{
|
||||
using( FileStream fs = File.OpenWrite(Path.Combine(tempPath, Path.GetRandomFileName())))
|
||||
using (FileStream fs = File.OpenWrite(Path.Combine(tempPath, Path.GetRandomFileName())))
|
||||
{
|
||||
fs.Write(Encoding.ASCII.GetBytes("This is a test"), 0, 14);
|
||||
fs.Flush();
|
||||
|
@ -47,21 +47,30 @@ namespace XenCenterLib.Archive
|
||||
/// <exception cref="NullReferenceException">If while combining path and current file name a null arises</exception>
|
||||
public void ExtractAllContents(string pathToExtractTo, Action cancellingDelegate = null)
|
||||
{
|
||||
if (String.IsNullOrEmpty(pathToExtractTo))
|
||||
if (string.IsNullOrEmpty(pathToExtractTo))
|
||||
throw new ArgumentNullException();
|
||||
|
||||
while (HasNext())
|
||||
{
|
||||
//Make the file path from the details in the archive making the path windows friendly
|
||||
string conflatedPath = Path.Combine(pathToExtractTo, CurrentFileName()).Replace('/', Path.DirectorySeparatorChar);
|
||||
//make the path Windows friendly
|
||||
var fileName = CurrentFileName();
|
||||
var isDirectory = IsDirectory();
|
||||
|
||||
//Create directories - empty ones will be made too
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(conflatedPath));
|
||||
var sanitizedName = fileName.Replace('/', Path.DirectorySeparatorChar);
|
||||
var conflatedPath = Path.Combine(pathToExtractTo, sanitizedName);
|
||||
|
||||
var dir = isDirectory ? conflatedPath : Path.GetDirectoryName(conflatedPath);
|
||||
dir = StringUtility.ToLongWindowsPath(dir, true);
|
||||
|
||||
//Create directory - empty one will be made too
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
//If we have a file extract the contents
|
||||
if (!IsDirectory())
|
||||
if (!isDirectory)
|
||||
{
|
||||
using (FileStream fs = File.Create(conflatedPath))
|
||||
conflatedPath = StringUtility.ToLongWindowsPath(conflatedPath, false);
|
||||
|
||||
using (var fs = File.Create(conflatedPath))
|
||||
ExtractCurrentFile(fs, cancellingDelegate);
|
||||
}
|
||||
}
|
||||
|
@ -35,47 +35,18 @@ namespace XenCenterLib.Archive
|
||||
{
|
||||
public abstract class ArchiveWriter : IDisposable
|
||||
{
|
||||
public abstract void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate);
|
||||
private int _progressTracker;
|
||||
|
||||
public abstract void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate);
|
||||
public abstract void AddDirectory(string directoryName, DateTime modificationTime);
|
||||
|
||||
public virtual void SetBaseStream(Stream outputStream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public abstract void AddDirectory(string directoryName, DateTime modificationTime);
|
||||
|
||||
/// <summary>
|
||||
/// Disposal hook
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing){ }
|
||||
|
||||
public void CreateArchive(string pathToArchive, Action cancellingDelegate = null, Action<int> progressDelegate = null)
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!Directory.Exists(pathToArchive))
|
||||
throw new FileNotFoundException("The path " + pathToArchive + " does not exist");
|
||||
|
||||
var files = Directory.GetFiles(pathToArchive, "*.*", SearchOption.AllDirectories);
|
||||
for (var i = 0; i < files.Length; i++)
|
||||
{
|
||||
string filePath = files[i];
|
||||
cancellingDelegate?.Invoke();
|
||||
|
||||
using (FileStream fs = File.OpenRead(filePath))
|
||||
{
|
||||
Add(fs, CleanRelativePathName(pathToArchive, filePath), File.GetCreationTime(filePath), cancellingDelegate);
|
||||
progressDelegate?.Invoke((int)50.0 * i / files.Length);
|
||||
}
|
||||
}
|
||||
|
||||
var directories = Directory.GetDirectories(pathToArchive, "*.*", SearchOption.AllDirectories);
|
||||
for (var j = 0; j < directories.Length; j++)
|
||||
{
|
||||
string dirPath = directories[j];
|
||||
cancellingDelegate?.Invoke();
|
||||
AddDirectory(CleanRelativePathName(pathToArchive, dirPath), Directory.GetCreationTime(dirPath));
|
||||
progressDelegate?.Invoke(50 + (int)50.0 * j / directories.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -84,10 +55,80 @@ namespace XenCenterLib.Archive
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private string CleanRelativePathName(string rootPath, string pathName)
|
||||
public void CreateArchive(string pathToArchive, Action cancellingDelegate = null, Action<int> progressDelegate = null)
|
||||
{
|
||||
return pathName.Replace(rootPath, "").Replace('\\', '/').TrimStart('/');
|
||||
// We look recursively and do not use Directory.GetDirectories and Directory.GetFiles with
|
||||
// AllDirectories options because in .NET 4.8 they do not enumerate all elements if they
|
||||
// have paths longer than 260 characters (248 for directories).
|
||||
// If moving to .NET Core, please consider reverting this.
|
||||
|
||||
_progressTracker = 0;
|
||||
PopulateArchive(pathToArchive, pathToArchive, cancellingDelegate, progressDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the archive by recursively calling the overridden <see cref="Add"/> and <see cref="AddDirectory"/>.
|
||||
/// </summary>
|
||||
/// <param name="pathToArchive">The path to the root of the folder we're archiving</param>
|
||||
/// <param name="pathToCurrentDirectory">Keeps track of the current directory we're archiving. In the first recursive call it should be the same as <see cref="pathToArchive"/></param>
|
||||
/// <param name="cancellingDelegate">Action cal led for cancelling</param>
|
||||
/// <param name="progressDelegate">Action for reporting progress. Method will populate its parameter with the current progress of the recursive operation</param>
|
||||
/// <param name="totalProgressIncrease">Total progress that needs to be added for archiving this directory. In the first recursive call it should be 100. If the folder we're adding should add 18 percentage points to the total progress, set this value to 18.</param>
|
||||
/// <param name="progressOffset">Offset to the progress. This is added to <see cref="totalProgressIncrease"/> when setting the progress for this directory. If this folder should add 18 percentage points to the total progress, but it's for a folder past the 50% mark of the total progress (i.e.: completing this folder should set the total to 68), set this value to 50.</param>
|
||||
/// <exception cref="FileNotFoundException">A directory could not be found.</exception>
|
||||
private void PopulateArchive(string pathToArchive, string pathToCurrentDirectory, Action cancellingDelegate = null, Action<int> progressDelegate = null, float totalProgressIncrease = 100, float progressOffset = 0)
|
||||
{
|
||||
cancellingDelegate?.Invoke();
|
||||
|
||||
pathToArchive = StringUtility.ToLongWindowsPath(pathToArchive, true);
|
||||
if (!Directory.Exists(pathToArchive))
|
||||
throw new FileNotFoundException($"The path {pathToArchive} does not exist");
|
||||
|
||||
pathToCurrentDirectory = StringUtility.ToLongWindowsPath(pathToCurrentDirectory, true);
|
||||
|
||||
//add the current directory except when it's the root directory
|
||||
if (pathToArchive != pathToCurrentDirectory)
|
||||
AddDirectory(CleanRelativePathName(pathToArchive, pathToCurrentDirectory), Directory.GetCreationTime(pathToCurrentDirectory));
|
||||
|
||||
var files = Directory.GetFiles(pathToCurrentDirectory);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
cancellingDelegate?.Invoke();
|
||||
|
||||
var filePath = StringUtility.ToLongWindowsPath(file, false);
|
||||
|
||||
using (var fs = File.OpenRead(filePath))
|
||||
Add(fs, CleanRelativePathName(pathToArchive, filePath), File.GetCreationTime(filePath), cancellingDelegate);
|
||||
}
|
||||
|
||||
if (_progressTracker != (int)progressOffset)
|
||||
{
|
||||
_progressTracker = (int)progressOffset;
|
||||
progressDelegate?.Invoke(_progressTracker);
|
||||
}
|
||||
|
||||
var directories = Directory.GetDirectories(pathToCurrentDirectory);
|
||||
if (directories.Length == 0)
|
||||
return;
|
||||
|
||||
float increment = totalProgressIncrease / directories.Length;
|
||||
|
||||
for (var i = 0; i < directories.Length; i++)
|
||||
{
|
||||
PopulateArchive(pathToArchive, directories[i], cancellingDelegate, progressDelegate,
|
||||
increment, i * increment + progressOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private string CleanRelativePathName(string rootPath, string pathName)
|
||||
{
|
||||
var cleanedRootPath = rootPath.Replace(@"\\?\", string.Empty);
|
||||
return pathName
|
||||
.Replace(@"\\?\", string.Empty)
|
||||
.Replace(cleanedRootPath, string.Empty)
|
||||
.Replace('\\', '/')
|
||||
.TrimStart('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,10 +77,10 @@ namespace XenCenterLib.Archive
|
||||
tar.CloseEntry();
|
||||
}
|
||||
|
||||
public override void Add(Stream filetoAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
|
||||
public override void Add(Stream fileToAdd, string fileName, DateTime modificationTime, Action cancellingDelegate)
|
||||
{
|
||||
TarEntry entry = TarEntry.CreateTarEntry(fileName);
|
||||
entry.Size = filetoAdd.Length;
|
||||
entry.Size = fileToAdd.Length;
|
||||
entry.ModTime = modificationTime;
|
||||
|
||||
tar.PutNextEntry(entry);
|
||||
@ -89,9 +89,9 @@ namespace XenCenterLib.Archive
|
||||
|
||||
//You have to do this because if using a memory stream the pointer will be at the end it
|
||||
//it's just been read and this will cause nothing to be written out
|
||||
filetoAdd.Position = 0;
|
||||
fileToAdd.Position = 0;
|
||||
|
||||
while ((n = filetoAdd.Read(buffer, 0, buffer.Length)) > 0)
|
||||
while ((n = fileToAdd.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
cancellingDelegate?.Invoke();
|
||||
tar.Write(buffer, 0, n);
|
||||
|
@ -33,6 +33,8 @@ using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace XenCenterLib
|
||||
{
|
||||
@ -218,5 +220,39 @@ namespace XenCenterLib
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be used to format file and directory paths for File streams. <br />
|
||||
/// Prepends \\?\ to path if it is longer than (MAX_PATH - 12) in Windows. This is the legacy directory length limit.<br /><br />
|
||||
/// See <see href='https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea'>CreateFileA</see> and <see href='https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry'>Maximum Path Length Limitation</see> for more info.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path</param>
|
||||
/// <param name="isDirectory">Whether the input path is a directory</param>
|
||||
public static string ToLongWindowsPath(string inputPath, bool isDirectory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputPath) || inputPath.StartsWith(@"\\?\"))
|
||||
return inputPath;
|
||||
|
||||
if (isDirectory)
|
||||
return inputPath.Length >= 248 ? ToLongWindowsPathUnchecked(inputPath) : inputPath;
|
||||
|
||||
var dir = Path.GetDirectoryName(inputPath);
|
||||
if (string.IsNullOrEmpty(dir))
|
||||
return inputPath;
|
||||
|
||||
return dir.Length >= 248 || inputPath.Length >= 260 ? ToLongWindowsPathUnchecked(inputPath) : inputPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be used to format file and directory paths for File streams. <br />
|
||||
/// Prepends \\?\ to path if it is longer than (MAX_PATH - 12) in Windows. This is the legacy directory length limit.<br /><br />
|
||||
/// See <see href='https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea'>CreateFileA</see> and
|
||||
/// <see href='https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry'>Maximum Path Length Limitation</see> for more info.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path</param>
|
||||
public static string ToLongWindowsPathUnchecked(string inputPath)
|
||||
{
|
||||
return $@"\\?\{inputPath}";
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using XenCenterLib;
|
||||
using XenCenterLib.Archive;
|
||||
|
||||
|
||||
@ -65,7 +66,7 @@ namespace XenAdmin.Actions
|
||||
public ZipStatusReportAction(string tempFolder, string destFile, string timeString = null, bool suppressHistory = true)
|
||||
: base(null, Messages.BUGTOOL_SAVING, destFile, timeString, suppressHistory)
|
||||
{
|
||||
_inputTempFolder = tempFolder;
|
||||
_inputTempFolder = StringUtility.ToLongWindowsPathUnchecked(tempFolder);
|
||||
_destFile = destFile;
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ namespace XenAdmin.Actions
|
||||
Status = ReportStatus.inProgress;
|
||||
do
|
||||
{
|
||||
_extractTempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
_extractTempDir = StringUtility.ToLongWindowsPathUnchecked(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
|
||||
} while (Directory.Exists(_extractTempDir));
|
||||
|
||||
Directory.CreateDirectory(_extractTempDir);
|
||||
|
Loading…
Reference in New Issue
Block a user