Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sdk/storage/Azure.Storage.DataMovement.Blobs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release History

## 12.2.2 (2025-09-10)

### Bugs Fixed
- Fixed an issue on upload transfers where file/directory names on the destination may be incorrect. The issue could occur if the path passed to `LocalFilesStorageResourceProvider.FromDirectory` contained a trailing slash.

## 12.2.1 (2025-08-06)

### Bugs Fixed
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/Azure.Storage.DataMovement.Blobs/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/storage/Azure.Storage.DataMovement.Blobs",
"Tag": "net/storage/Azure.Storage.DataMovement.Blobs_f1a4120258"
"Tag": "net/storage/Azure.Storage.DataMovement.Blobs_c2f1f2fc3f"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyTitle>Microsoft Azure.Storage.DataMovement.Blobs client library</AssemblyTitle>
<Version>12.2.1</Version>
<Version>12.2.2</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>12.2.0</ApiCompatVersion>
<DefineConstants>BlobDataMovementSDK;$(DefineConstants)</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task VerifyStartUploadDirectoryAsync([Values] bool addBlobDirectory

var blobUri = new Uri(accountUrl + (addBlobDirectoryPath ? containerName + "/" + blobDirectoryPrefix : containerName));

var directoryPath = Path.GetTempPath();
var directoryPath = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);

var options = addTransferOptions ? new TransferOptions() : (TransferOptions)null;

Expand Down Expand Up @@ -91,7 +91,7 @@ public async Task VerifyStartDownloadToDirectoryAsync([Values] bool addBlobDirec

var blobUri = new Uri(accountUrl + (addBlobDirectoryPath ? containerName + "/" + blobDirectoryPrefix : containerName));

var directoryPath = Path.GetTempPath();
var directoryPath = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);

var options = addTransferOptions ? new TransferOptions() : (TransferOptions)null;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release History

## 12.2.2 (2025-09-10)

### Bugs Fixed
- Fixed an issue on upload transfers where file/directory names on the destination may be incorrect. The issue could occur if the path passed to `LocalFilesStorageResourceProvider.FromDirectory` contained a trailing slash.

## 12.2.1 (2025-08-06)

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/storage/Azure.Storage.DataMovement.Files.Shares",
"Tag": "net/storage/Azure.Storage.DataMovement.Files.Shares_90fc7c3256"
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/storage/Azure.Storage.DataMovement.Files.Shares",
"Tag": "net/storage/Azure.Storage.DataMovement.Files.Shares_b7d8da3dcc"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyTitle>Microsoft Azure.Storage.DataMovement.Files.Shares client library</AssemblyTitle>
<Version>12.2.1</Version>
<Version>12.2.2</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>12.2.0</ApiCompatVersion>
<DefineConstants>ShareDataMovementSDK;$(DefineConstants)</DefineConstants>
Expand Down
5 changes: 5 additions & 0 deletions sdk/storage/Azure.Storage.DataMovement/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release History

## 12.2.2 (2025-09-10)

### Bugs Fixed
- Fixed an issue on upload transfers where file/directory names on the destination may be incorrect. The issue could occur if the path passed to `LocalFilesStorageResourceProvider.FromDirectory` contained a trailing slash.

## 12.2.1 (2025-08-06)

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyTitle>Microsoft Azure.Storage.DataMovement client library</AssemblyTitle>
<Version>12.2.1</Version>
<Version>12.2.2</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>12.2.0</ApiCompatVersion>
<DefineConstants>DataMovementSDK;$(DefineConstants)</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal class LocalDirectoryStorageResourceContainer : StorageResourceContainer
public LocalDirectoryStorageResourceContainer(string path)
{
Argument.AssertNotNullOrWhiteSpace(path, nameof(path));
path = path.TrimEnd(Path.DirectorySeparatorChar);
_uri = PathScanner.GetEncodedUriFromPath(path);
}

Expand Down
24 changes: 13 additions & 11 deletions sdk/storage/Azure.Storage.DataMovement/src/TransferJobInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,7 @@ private async IAsyncEnumerable<JobPartInternal> EnumerateAndCreateJobPartsAsync(

if (current.IsContainer)
{
// Create sub-container
string containerUriPath = _sourceResourceContainer.Uri.GetPath();
string subContainerPath = string.IsNullOrEmpty(containerUriPath)
? current.Uri.GetPath()
: current.Uri.GetPath().Substring(containerUriPath.Length + 1);
// Decode the container name as it was pulled from encoded Uri and will be re-encoded on destination.
subContainerPath = Uri.UnescapeDataString(subContainerPath);
string subContainerPath = GetChildResourcePath(_sourceResourceContainer, current);
StorageResourceContainer subContainer =
_destinationResourceContainer.GetChildStorageResourceContainer(subContainerPath);

Expand Down Expand Up @@ -369,10 +363,7 @@ private async IAsyncEnumerable<JobPartInternal> EnumerateAndCreateJobPartsAsync(
// Real container trasnfer
else
{
string containerUriPath = _sourceResourceContainer.Uri.GetPath();
sourceName = current.Uri.GetPath().Substring(containerUriPath.Length + 1);
// Decode the resource name as it was pulled from encoded Uri and will be re-encoded on destination.
sourceName = Uri.UnescapeDataString(sourceName);
sourceName = GetChildResourcePath(_sourceResourceContainer, current);
}

StorageResourceItem sourceItem = (StorageResourceItem)current;
Expand Down Expand Up @@ -653,5 +644,16 @@ internal async ValueTask IncrementJobParts()
{
await _progressTracker.IncrementQueuedFilesAsync(_cancellationToken).ConfigureAwait(false);
}

private static string GetChildResourcePath(StorageResourceContainer parent, StorageResource child)
{
string parentPath = parent.Uri.GetPath();
string childPath = child.Uri.GetPath().Substring(parentPath.Length);
// If container path does not contain a '/' (normal case), then childPath will have one after substring.
// Safe to use / here as we are using AbsolutePath which normalizes to /.
childPath = childPath.TrimStart('/');
// Decode the resource name as it was pulled from encoded Uri and will be re-encoded on destination.
return Uri.UnescapeDataString(childPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@ public LocalDirectoryStorageResourceTests(bool async)
: base(async, null /* TestMode.Record /* to re-record */)
{ }

private string[] fileNames => new[]
{
"C:\\Users\\user1\\Documents\\directory",
"C:\\Users\\user1\\Documents\\directory1\\",
"/user1/Documents/directory",
};

[Test]
public void Ctor_string()
{
string[] fileNames =
{
"C:\\Users\\user1\\Documents\\directory",
"/user1/Documents/directory",
};
foreach (string path in fileNames)
{
// Arrange
Expand All @@ -43,25 +41,29 @@ public void Ctor_string()
[TestCase("C:\\test\\path=true@&#%", "C:/test/path%3Dtrue%40%26%23%25")]
[TestCase("C:\\test\\path%3Dtest%26", "C:/test/path%253Dtest%2526")]
[TestCase("C:\\test\\folder with spaces", "C:/test/folder%20with%20spaces")]
[TestCase("X:\\testing\\test\\", "X:/testing/test")]
[TestCase("X:\\testing\\test\\\\", "X:/testing/test")]
public void Ctor_String_Encoding_Windows(string path, string absolutePath)
{
LocalDirectoryStorageResourceContainer storageResource = new(path);
Assert.That(storageResource.Uri.AbsolutePath, Is.EqualTo(absolutePath));
// LocalPath should equal original path
Assert.That(storageResource.Uri.LocalPath, Is.EqualTo(path));
// LocalPath should equal original path (trimmed)
Assert.That(storageResource.Uri.LocalPath, Is.EqualTo(path.TrimEnd('\\')));
}

[Test]
[RunOnlyOnPlatforms(Linux = true, OSX = true)]
[TestCase("/test/path=true@&#%", "/test/path%3Dtrue%40%26%23%25")]
[TestCase("/test/path%3Dtest%26", "/test/path%253Dtest%2526")]
[TestCase("/test/folder with spaces", "/test/folder%20with%20spaces")]
[TestCase("/testing/test/", "/testing/test")]
[TestCase("/testing/test//", "/testing/test")]
public void Ctor_String_Encoding_Unix(string path, string absolutePath)
{
LocalDirectoryStorageResourceContainer storageResource = new(path);
Assert.That(storageResource.Uri.AbsolutePath, Is.EqualTo(absolutePath));
// LocalPath should equal original path
Assert.That(storageResource.Uri.LocalPath, Is.EqualTo(path));
// LocalPath should equal original path (trimmed)
Assert.That(storageResource.Uri.LocalPath, Is.EqualTo(path.TrimEnd('/')));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Azure.Core;
using Azure.Core.TestFramework;
using Azure.Storage.Common;
using Azure.Storage.Test;
using Azure.Storage.Test.Shared;
using NUnit.Framework;

Expand Down Expand Up @@ -141,7 +142,8 @@ private async Task DownloadDirectoryAndVerifyAsync(
string directoryName = default,
TransferManagerOptions transferManagerOptions = default,
TransferOptions options = default,
CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default,
bool trailingSlash = false)
{
await SetupSourceDirectoryAsync(sourceContainer, sourcePrefix, itemSizes, cancellationToken);

Expand All @@ -157,7 +159,8 @@ private async Task DownloadDirectoryAndVerifyAsync(
};

StorageResourceContainer sourceResource = GetStorageResourceContainer(sourceContainer, sourcePrefix);
StorageResourceContainer destinationResource = LocalFilesStorageResourceProvider.FromDirectory(disposingLocalDirectory.DirectoryPath);
StorageResourceContainer destinationResource = LocalFilesStorageResourceProvider.FromDirectory(
disposingLocalDirectory.DirectoryPath + (trailingSlash ? Path.DirectorySeparatorChar : string.Empty));

await new TransferValidator().TransferAndVerifyAsync(
sourceResource,
Expand Down Expand Up @@ -407,14 +410,28 @@ public async Task DownloadDirectoryAsync_SpecialChars(string prefix)
string.Join("/", prefix, "space folder", "space file"),
];

CancellationTokenSource cts = new();
cts.CancelAfter(TimeSpan.FromSeconds(30));
CancellationToken cancellationToken = TestHelper.GetTimeoutToken(30);
await DownloadDirectoryAndVerifyAsync(
test.Container,
prefix,
itemNames.Select(name => (name, Constants.KB)).ToList(),
directoryName: directoryName,
cancellationToken: cts.Token).ConfigureAwait(false);
cancellationToken: cancellationToken);
}

[Test]
public async Task DownloadDirectoryAsync_TrailingSlash()
{
await using IDisposingContainer<TContainerClient> test = await GetDisposingContainerAsync();

string[] items = { "file1", "file2", "dir1/file1" };

CancellationToken cancellationToken = TestHelper.GetTimeoutToken(30);
await DownloadDirectoryAndVerifyAsync(
test.Container,
string.Empty,
items.Select(name => (name, Constants.KB)).ToList(),
cancellationToken: cancellationToken);
}
#endregion DirectoryDownloadTests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,5 +523,28 @@ await UploadDirectoryAndVerifyAsync(
expectedTransfers: files.Count,
cancellationToken: cancellationToken);
}

[RecordedTest]
public async Task Upload_TrailingSlash()
{
using DisposingLocalDirectory disposingLocalDirectory = DisposingLocalDirectory.GetTestDirectory();
await using IDisposingContainer<TContainerClient> test = await GetDisposingContainerAsync();

List<string> files = [ "file1", "file2", "dir1/file1" ];

CancellationToken cancellationToken = TestHelper.GetTimeoutToken(30);
await SetupDirectoryAsync(
disposingLocalDirectory.DirectoryPath,
files.Select(path => (path, (long)Constants.KB)).ToList(),
cancellationToken);

// Intentionally append trailing slash
string sourcePath = disposingLocalDirectory.DirectoryPath + Path.DirectorySeparatorChar;
await UploadDirectoryAndVerifyAsync(
sourcePath,
test.Container,
expectedTransfers: files.Count,
cancellationToken: cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public Task<Stream> OpenReadAsync(CancellationToken cancellationToken)

public static ListFilesAsync GetLocalFileLister(string directoryPath)
{
directoryPath = directoryPath.TrimEnd(Path.DirectorySeparatorChar);
Task<List<IResourceEnumerationItem>> ListFiles(CancellationToken cancellationToken)
{
List<IResourceEnumerationItem> result = new();
Expand Down