Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 003e1ef

Browse files
authored
Temp file handling (#397)
* Added temp files sources * Methods for easier reading and writing of the contents of the temp files. * Moved bits to `FileStreamUtility` * Moved path utility to validation job common. Added tests. * TempFile with subdirectories * Documentation fixups
1 parent 6a49c46 commit 003e1ef

13 files changed

Lines changed: 499 additions & 0 deletions

src/Validation.Common.Job/FileStreamUtility.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,14 @@ public static FileStream GetTemporaryFile()
1919
BufferSize,
2020
FileOptions.DeleteOnClose | FileOptions.Asynchronous);
2121
}
22+
23+
public static FileStream OpenTemporaryFile(string fileName)
24+
=> new FileStream(
25+
fileName,
26+
FileMode.Open,
27+
FileAccess.Read,
28+
FileShare.None,
29+
BufferSize,
30+
FileOptions.DeleteOnClose | FileOptions.Asynchronous);
2231
}
2332
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
7+
namespace NuGet.Jobs.Validation
8+
{
9+
public static class PathUtility
10+
{
11+
/// <summary>
12+
/// Checks if given file path is an absolute path
13+
/// </summary>
14+
public static bool IsFilePathAbsolute(string path)
15+
{
16+
if (path == null)
17+
{
18+
throw new ArgumentNullException(nameof(path));
19+
}
20+
21+
return Path.IsPathRooted(path)
22+
&& Path.GetPathRoot(path).Length == 3
23+
&& Path.GetPathRoot(path).Substring(1) == ":" + Path.DirectorySeparatorChar;
24+
}
25+
}
26+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.IO;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace NuGet.Jobs.Validation
9+
{
10+
/// <summary>
11+
/// Opens existing file for exclusive read and deletes it on close.
12+
/// </summary>
13+
public class DeleteOnCloseReadOnlyTempFile : ITempReadOnlyFile
14+
{
15+
private const int BufferSize = 8192;
16+
private readonly FileStream _fileStream;
17+
18+
public DeleteOnCloseReadOnlyTempFile(string fileName)
19+
{
20+
_fileStream = FileStreamUtility.OpenTemporaryFile(fileName);
21+
}
22+
23+
public async Task<string> ReadToEndAsync()
24+
{
25+
using (var streamReader = new StreamReader(ReadStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: BufferSize, leaveOpen: true))
26+
{
27+
return await streamReader.ReadToEndAsync();
28+
}
29+
}
30+
31+
public Stream ReadStream => _fileStream;
32+
33+
public string FullName => _fileStream.Name;
34+
35+
public void Dispose()
36+
{
37+
_fileStream.Dispose();
38+
}
39+
}
40+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace NuGet.Jobs.Validation
7+
{
8+
/// <summary>
9+
/// A wrapper for a temp file that tries to delete it on dispose
10+
/// </summary>
11+
public interface ITempFile : IDisposable
12+
{
13+
/// <summary>
14+
/// Full path to a temporary file
15+
/// </summary>
16+
string FullName { get; }
17+
}
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace NuGet.Jobs.Validation
5+
{
6+
/// <summary>
7+
/// Provides temp files for use
8+
/// </summary>
9+
public interface ITempFileFactory
10+
{
11+
/// <summary>
12+
/// Creates empty temp file under specified subdirectory of the temp directry,
13+
/// returns object that contains path to it and controls its lifetime.
14+
/// </summary>
15+
ITempFile CreateTempFile(string directoryName);
16+
17+
/// <summary>
18+
/// Creates temp file with specified text in the specified subdirectory of the temp directory,
19+
/// returns object that contains path to it and controls its lifetime.
20+
/// </summary>
21+
/// <param name="contents">The contents of the file to be created.</param>
22+
ITempFile CreateTempFile(string directoryName, string contents);
23+
24+
/// <summary>
25+
/// Opens existing file for reading and makes sure it is deleted on closing.
26+
/// </summary>
27+
ITempReadOnlyFile OpenFileForReadAndDelete(string fileName);
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
7+
namespace NuGet.Jobs.Validation
8+
{
9+
/// <summary>
10+
/// A temp file interface that provides stream for reading its content.
11+
/// </summary>
12+
public interface ITempReadOnlyFile : ITempFile
13+
{
14+
Stream ReadStream { get; }
15+
16+
/// <summary>
17+
/// Reads the remaining content of the <see cref="ReadStream"/> as string and returns it.
18+
/// </summary>
19+
Task<string> ReadToEndAsync();
20+
}
21+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
7+
namespace NuGet.Jobs.Validation
8+
{
9+
public class TempFile : ITempFile
10+
{
11+
public TempFile()
12+
{
13+
FullName = Path.GetTempFileName();
14+
}
15+
16+
private TempFile(string fullName)
17+
{
18+
FullName = fullName;
19+
}
20+
21+
/// <summary>
22+
/// Creates temp file under specified subdirectory in the temp directory.
23+
/// Creates missing directories as needed.
24+
/// </summary>
25+
/// <param name="directoryName">Subdirectory name under temp directory to create file in.</param>
26+
public static TempFile Create(string directoryName)
27+
{
28+
if (directoryName == null)
29+
{
30+
throw new ArgumentNullException(nameof(directoryName));
31+
}
32+
33+
if (PathUtility.IsFilePathAbsolute(directoryName))
34+
{
35+
throw new ArgumentException($"Directory name must be a relative path, passed value: {directoryName}", nameof(directoryName));
36+
}
37+
38+
var directory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), directoryName));
39+
if (!directory.Exists)
40+
{
41+
directory.Create();
42+
}
43+
44+
string fileName;
45+
do
46+
{
47+
fileName = Path.Combine(directory.FullName, $"{Guid.NewGuid()}.tmp");
48+
} while (File.Exists(fileName));
49+
File.Create(fileName).Dispose();
50+
51+
return new TempFile(fileName);
52+
}
53+
54+
public string FullName { get; }
55+
56+
public void Dispose()
57+
{
58+
try
59+
{
60+
// we'll try to delete file if it exists...
61+
if (File.Exists(FullName))
62+
{
63+
File.Delete(FullName);
64+
}
65+
}
66+
catch
67+
{
68+
// ... but won't throw if anything goes wrong
69+
}
70+
}
71+
}
72+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.IO;
5+
using System.Text;
6+
7+
namespace NuGet.Jobs.Validation
8+
{
9+
public class TempFileFactory : ITempFileFactory
10+
{
11+
public ITempFile CreateTempFile(string directoryName)
12+
=> TempFile.Create(directoryName);
13+
14+
public ITempFile CreateTempFile(string directoryName, string contents)
15+
{
16+
var file = CreateTempFile(directoryName);
17+
18+
File.WriteAllText(file.FullName, contents, Encoding.UTF8);
19+
20+
return file;
21+
}
22+
23+
public ITempReadOnlyFile OpenFileForReadAndDelete(string fileName)
24+
=> new DeleteOnCloseReadOnlyTempFile(fileName);
25+
}
26+
}

src/Validation.Common.Job/Validation.Common.Job.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<Compile Include="LoggerDiagnosticsService.cs" />
4646
<Compile Include="LoggerDiagnosticsSource.cs" />
4747
<Compile Include="PackageDownloader.cs" />
48+
<Compile Include="PathUtility.cs" />
4849
<Compile Include="Properties\AssemblyInfo.cs" />
4950
<Compile Include="Properties\AssemblyInfo.*.cs" />
5051
<Compile Include="Storage\AddStatusResult.cs" />
@@ -63,6 +64,12 @@
6364
<Compile Include="Validation\ValidatorName.cs" />
6465
<Compile Include="Validation\ValidatorNameAttribute.cs" />
6566
<Compile Include="Validation\ValidatorUtility.cs" />
67+
<Compile Include="TempFiles\DeleteOnCloseReadOnlyTempFile.cs" />
68+
<Compile Include="TempFiles\ITempFile.cs" />
69+
<Compile Include="TempFiles\ITempFileFactory.cs" />
70+
<Compile Include="TempFiles\ITempReadOnlyFile.cs" />
71+
<Compile Include="TempFiles\TempFile.cs" />
72+
<Compile Include="TempFiles\TempFileFactory.cs" />
6673
</ItemGroup>
6774
<ItemGroup>
6875
<PackageReference Include="Autofac">
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using NuGet.Jobs.Validation;
6+
using Xunit;
7+
8+
namespace Validation.Common.Job.Tests
9+
{
10+
public class PathUtilityFacts
11+
{
12+
[Fact]
13+
public void IsFilePathAbsoluteThrowsWhenPathIsNull()
14+
{
15+
Assert.Throws<ArgumentNullException>(() => PathUtility.IsFilePathAbsolute(null));
16+
}
17+
18+
[Theory]
19+
[InlineData(@"", false)]
20+
[InlineData(@"c:", false)]
21+
[InlineData(@"aaa.txt", false)]
22+
[InlineData(@"bbb\aaa.txt", false)]
23+
[InlineData(@"c:aaa.txt", false)]
24+
[InlineData(@"c:bbb\aaa.txt", false)]
25+
[InlineData(@"c:\", true)]
26+
[InlineData(@"c:\aaa.txt", true)]
27+
[InlineData(@"c:\bbb\aaa.txt", true)]
28+
public void IsFilePathAbsoluteSmokeTest(string path, bool expectedResult)
29+
{
30+
var result = PathUtility.IsFilePathAbsolute(path);
31+
Assert.Equal(expectedResult, result);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)