Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,41 @@ async Task<string> InternalSaveAsync(string initialPath, string fileName, Stream
}

var fileUrl = tempDirectoryPath.Append(fileName, false);
await WriteStream(stream, fileUrl.Path ?? throw new Exception("Path cannot be null."), progress, cancellationToken);
try
{
await WriteStream(stream, fileUrl.Path ?? throw new Exception("Path cannot be null."), progress, cancellationToken);

cancellationToken.ThrowIfCancellationRequested();
taskCompetedSource?.TrySetCanceled(CancellationToken.None);
var tcs = taskCompetedSource = new(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
taskCompetedSource?.TrySetCanceled(CancellationToken.None);
var tcs = taskCompetedSource = new(cancellationToken);

documentPickerViewController = new([fileUrl])
{
DirectoryUrl = NSUrl.FromString(initialPath)
};
documentPickerViewController.DidPickDocumentAtUrls += DocumentPickerViewControllerOnDidPickDocumentAtUrls;
documentPickerViewController.WasCancelled += DocumentPickerViewControllerOnWasCancelled;
documentPickerViewController = new([fileUrl], true)
{
DirectoryUrl = NSUrl.FromString(initialPath)
};
documentPickerViewController.DidPickDocumentAtUrls += DocumentPickerViewControllerOnDidPickDocumentAtUrls;
documentPickerViewController.WasCancelled += DocumentPickerViewControllerOnWasCancelled;

var currentViewController = Platform.GetCurrentUIViewController();
if (currentViewController is not null)
{
currentViewController.PresentViewController(documentPickerViewController, true, null);
var currentViewController = Platform.GetCurrentUIViewController();
if (currentViewController is not null)
{
currentViewController.PresentViewController(documentPickerViewController, true, () =>
{
fileManager.Remove(tempDirectoryPath, out _);
});
Comment on lines +50 to +53
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The temporary directory cleanup in the completion callback may execute prematurely before the file picker operations complete. The completion callback of PresentViewController is invoked immediately after the presentation animation finishes, not after the user completes their interaction with the document picker.

This means the temp directory (and the file within it) could be deleted while the UIDocumentPickerViewController is still displaying and before the user has saved the file to their chosen location, which would cause the save operation to fail.

Consider removing the cleanup from the completion callback and instead perform the cleanup in the event handlers (DocumentPickerViewControllerOnDidPickDocumentAtUrls and DocumentPickerViewControllerOnWasCancelled) where the actual file picker interaction is complete.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VladislavAntonyuk what are your thoughts on this? I had the cleanup method, which could be called in a finally or as part of InternalDispose, that will always check for the file and remove it if it exists. If we restore that, it's always safe to call, and can be called from any catch or finally block, from dispose, or from the event handlers suggested here.

}
else
{
throw new FileSaveException("Unable to get a window where to present the file saver UI.");
}

return await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
}
else
catch
{
throw new FileSaveException("Unable to get a window where to present the file saver UI.");
fileManager.Remove(tempDirectoryPath, out _);
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch block doesn't handle cleanup of event handlers or dispose the document picker. If an exception occurs after the event handlers are attached (lines 44-45), these handlers will remain attached and could cause memory leaks or unexpected behavior in subsequent operations.

Consider calling InternalDispose() in the catch block to ensure proper cleanup of all resources.

Suggested change
fileManager.Remove(tempDirectoryPath, out _);
fileManager.Remove(tempDirectoryPath, out _);
InternalDispose();

Copilot uses AI. Check for mistakes.
throw;
}

return await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
}

Task<string> InternalSaveAsync(string fileName, Stream stream, IProgress<double>? progress, CancellationToken cancellationToken)
Expand Down
Loading