Skip to content

Fixed an issue preventing to drag&drop a link from a browser addressbar to Files #9388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 14, 2022
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
1 change: 1 addition & 0 deletions src/Files.Uwp/Files.Uwp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
<Compile Include="Filesystem\StorageItems\BaseQueryResults.cs" />
<Compile Include="Filesystem\StorageItems\FtpStorageFile.cs" />
<Compile Include="Filesystem\StorageItems\FtpStorageFolder.cs" />
<Compile Include="Filesystem\StorageItems\NativeStorageFile.cs" />
<Compile Include="Filesystem\StorageItems\ShellStorageFile.cs" />
<Compile Include="Filesystem\StorageItems\ShellStorageFolder.cs" />
<Compile Include="Filesystem\StorageItems\StreamWithContentType.cs" />
Expand Down
279 changes: 279 additions & 0 deletions src/Files.Uwp/Filesystem/StorageItems/NativeStorageFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
using Files.Uwp.Helpers;
using Microsoft.Toolkit.Uwp;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using IO = System.IO;
using Storage = Windows.Storage;

namespace Files.Uwp.Filesystem.StorageItems
{
/// <summary>
/// Shortcuts and alternate data stream.
/// Uses *FromApp methods for file operations
/// </summary>
public class NativeStorageFile : BaseStorageFile
{
public override string Path { get; }
public override string Name { get; }
public override string DisplayName => Name;
public override string ContentType => "application/octet-stream";
public override string FileType => IO.Path.GetExtension(Name);
public override string FolderRelativeId => $"0\\{Name}";

public bool IsShortcut => FileType.Equals(".lnk", StringComparison.OrdinalIgnoreCase) || FileType.Equals(".url", StringComparison.OrdinalIgnoreCase);
public bool IsAlternateStream => System.Text.RegularExpressions.Regex.IsMatch(Path, @"\w:\w");

public override string DisplayType
{
get
{
var itemType = "ItemTypeFile".GetLocalized();
if (Name.Contains(".", StringComparison.Ordinal))
{
itemType = IO.Path.GetExtension(Name).Trim('.') + " " + itemType;
}
return itemType;
}
}

public override DateTimeOffset DateCreated { get; }
public override Storage.FileAttributes Attributes { get; } = Storage.FileAttributes.Normal;
public override IStorageItemExtraProperties Properties => new BaseBasicStorageItemExtraProperties(this);

public NativeStorageFile(string path, string name, DateTimeOffset dateCreated)
{
Path = path;
Name = name;
DateCreated = dateCreated;
}

public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace)
=> throw new NotSupportedException();

public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destinationFolder)
=> CopyAsync(destinationFolder, Name, NameCollisionOption.FailIfExists);

public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName)
=> CopyAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);

public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
{
return AsyncInfo.Run<BaseStorageFile>(async (cancellationToken) =>
{
if (string.IsNullOrEmpty(destinationFolder.Path))
{
throw new NotSupportedException();
}
var destination = IO.Path.Combine(destinationFolder.Path, desiredNewName);
var destFile = new NativeStorageFile(destination, desiredNewName, DateTime.Now);
if (!IsAlternateStream)
{
if (!await Task.Run(() => NativeFileOperationsHelper.CopyFileFromApp(Path, destination, option != NameCollisionOption.ReplaceExisting)))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
destFile.CreateFile();
using (var inStream = await this.OpenStreamForReadAsync())
using (var outStream = await destFile.OpenStreamForWriteAsync())
{
await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
}
}
return destFile;
});
}

private void CreateFile()
{
using var hFile = NativeFileOperationsHelper.CreateFileForWrite(Path, false);
if (hFile.IsInvalid)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}

public override IAsyncAction DeleteAsync()
{
return AsyncInfo.Run(async (cancellationToken) =>
{
if (!NativeFileOperationsHelper.DeleteFileFromApp(Path))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
});
}

public override IAsyncAction DeleteAsync(StorageDeleteOption option)
{
if (option == StorageDeleteOption.PermanentDelete)
{
return DeleteAsync();
}
throw new NotSupportedException();
}

public override IAsyncOperation<BaseBasicProperties> GetBasicPropertiesAsync()
{
return AsyncInfo.Run(async (cancellationToken) =>
{
return new BaseBasicProperties();
});
}

public override IAsyncOperation<BaseStorageFolder> GetParentAsync()
=> throw new NotSupportedException();

public override IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(ThumbnailMode mode)
=> throw new NotSupportedException();

public override IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(ThumbnailMode mode, uint requestedSize)
=> throw new NotSupportedException();

public override IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options)
=> throw new NotSupportedException();

public static IAsyncOperation<BaseStorageFile> FromPathAsync(string path)
{
return AsyncInfo.Run<BaseStorageFile>(async (cancellationToken) =>
{
if (NativeStorageFile.IsNativePath(path))
{
if (CheckAccess(path))
{
var name = IO.Path.GetFileName(path);
return new NativeStorageFile(path, name.Substring(name.LastIndexOf(":") + 1), DateTime.Now);
}
}
return null;
});
}

private static bool CheckAccess(string path)
{
using var hFile = NativeFileOperationsHelper.OpenFileForRead(path);
return !hFile.IsInvalid;
}

private static bool IsNativePath(string path)
{
var isShortcut = path.EndsWith(".lnk", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".url", StringComparison.OrdinalIgnoreCase);
var isAlternateStream = System.Text.RegularExpressions.Regex.IsMatch(path, @"\w:\w");
return isShortcut || isAlternateStream;
}

public override bool IsEqual(IStorageItem item) => item?.Path == Path;
public override bool IsOfType(StorageItemTypes type) => type is StorageItemTypes.File;

public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace)
=> throw new NotSupportedException();

public override IAsyncAction MoveAsync(IStorageFolder destinationFolder)
=> MoveAsync(destinationFolder, Name, NameCollisionOption.FailIfExists);

public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName)
=> MoveAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);

public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
{
return AsyncInfo.Run(async (cancellationToken) =>
{
if (string.IsNullOrEmpty(destinationFolder.Path))
{
throw new NotSupportedException();
}
var destination = IO.Path.Combine(destinationFolder.Path, desiredNewName);
if (!IsAlternateStream)
{
if (!await Task.Run(() => NativeFileOperationsHelper.MoveFileFromApp(Path, destination)))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
await CopyAsync(destinationFolder, desiredNewName, option);
await DeleteAsync();
}
});
}

public override IAsyncOperation<IRandomAccessStream> OpenAsync(FileAccessMode accessMode)
{
return AsyncInfo.Run<IRandomAccessStream>(async (cancellationToken) =>
{
var hFile = NativeFileOperationsHelper.OpenFileForRead(Path, accessMode == FileAccessMode.ReadWrite);
return new FileStream(hFile, accessMode == FileAccessMode.ReadWrite ? FileAccess.ReadWrite : FileAccess.Read).AsRandomAccessStream();
});
}

public override IAsyncOperation<IRandomAccessStream> OpenAsync(FileAccessMode accessMode, StorageOpenOptions options) => OpenAsync(accessMode);

public override IAsyncOperation<IRandomAccessStreamWithContentType> OpenReadAsync()
{
return AsyncInfo.Run<IRandomAccessStreamWithContentType>(async (cancellationToken) =>
{
return new StreamWithContentType(await OpenAsync(FileAccessMode.Read));
});
}

public override IAsyncOperation<IInputStream> OpenSequentialReadAsync()
{
return AsyncInfo.Run(async (cancellationToken) =>
{
var hFile = NativeFileOperationsHelper.OpenFileForRead(Path);
return new FileStream(hFile, FileAccess.Read).AsInputStream();
});
}

public override IAsyncOperation<StorageStreamTransaction> OpenTransactedWriteAsync()
=> throw new NotSupportedException();

public override IAsyncOperation<StorageStreamTransaction> OpenTransactedWriteAsync(StorageOpenOptions options)
=> throw new NotSupportedException();

public override IAsyncAction RenameAsync(string desiredName)
=> RenameAsync(desiredName, NameCollisionOption.FailIfExists);

public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option)
{
return AsyncInfo.Run(async (cancellationToken) =>
{
string destination = IO.Path.Combine(IO.Path.GetDirectoryName(Path), desiredName);
var destFile = new NativeStorageFile(destination, desiredName, DateTime.Now);
if (!IsAlternateStream)
{
if (!await Task.Run(() => NativeFileOperationsHelper.MoveFileFromApp(Path, destination)))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
destFile.CreateFile();
using (var inStream = await this.OpenStreamForReadAsync())
using (var outStream = await destFile.OpenStreamForWriteAsync())
{
await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
}
await DeleteAsync();
}
});
}

public override IAsyncOperation<StorageFile> ToStorageFileAsync()
=> throw new NotSupportedException();
}
}
51 changes: 34 additions & 17 deletions src/Files.Uwp/Filesystem/StorageItems/SystemStorageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using Storage = Windows.Storage;
using IO = System.IO;

namespace Files.Uwp.Filesystem.StorageItems
{
Expand Down Expand Up @@ -61,19 +62,42 @@ public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destin
return AsyncInfo.Run(async (cancellationToken) =>
{
var destFolder = destinationFolder.AsBaseStorageFolder(); // Avoid calling IStorageFolder method
if (destFolder is SystemStorageFolder sysFolder)
try
{
// File created by CreateFileAsync will get immediately deleted on MTP?! (#7206)
return await File.CopyAsync(sysFolder.Folder, desiredNewName, option);
if (destFolder is SystemStorageFolder sysFolder)
{
// File created by CreateFileAsync will get immediately deleted on MTP?! (#7206)
return await File.CopyAsync(sysFolder.Folder, desiredNewName, option);
}
var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert());
using (var inStream = await this.OpenStreamForReadAsync())
using (var outStream = await destFile.OpenStreamForWriteAsync())
{
await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
}
return destFile;
}
var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert());
using (var inStream = await this.OpenStreamForReadAsync())
using (var outStream = await destFile.OpenStreamForWriteAsync())
catch (UnauthorizedAccessException ex) // shortcuts & .url
{
await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
if (!string.IsNullOrEmpty(destFolder.Path))
{
var destination = IO.Path.Combine(destFolder.Path, desiredNewName);
var hFile = NativeFileOperationsHelper.CreateFileForWrite(destination,
option == NameCollisionOption.ReplaceExisting);
if (!hFile.IsInvalid)
{
using (var inStream = await this.OpenStreamForReadAsync())
using (var outStream = new FileStream(hFile, FileAccess.Write))
{
await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
}
return new NativeStorageFile(destination, desiredNewName, DateTime.Now);
}
}
throw ex;
}
return destFile;
});
}

Expand Down Expand Up @@ -101,14 +125,7 @@ public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string
await File.MoveAsync(sysFolder.Folder, desiredNewName, option);
return;
}

var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert());

using var inStream = await this.OpenStreamForReadAsync();
using var outStream = await destFile.OpenStreamForWriteAsync();

await inStream.CopyToAsync(outStream);
await outStream.FlushAsync();
await CopyAsync(destinationFolder, desiredNewName, option);
// Move unsupported, copy but do not delete original
});
}
Expand Down